xref: /illumos-gate/usr/src/lib/libjedec/common/libjedec_spd_lp4.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2024 Oxide Computer Company
14  */
15 
16 /*
17  * LPDDR4, LPDDR4X, and LPDDR3 SPD processing logic. For an overview of the
18  * processing design please see libjedec_spd.c. These have a similar design to
19  * DDR4 and leverages the existing manufacturing logic there.
20  */
21 
22 #include <sys/sysmacros.h>
23 #include <sys/debug.h>
24 #include "libjedec_spd.h"
25 
26 static const spd_value_map_t spd_lp4_nbytes_used_map[] = {
27 	{ SPD_LP4_NBYTES_USED_UNDEF, 0, true },
28 	{ SPD_LP4_NBYTES_USED_128, 128, false },
29 	{ SPD_LP4_NBYTES_USED_256, 256, false },
30 	{ SPD_LP4_NBYTES_USED_384, 384, false },
31 	{ SPD_LP4_NBYTES_USED_512, 512, false }
32 };
33 
34 static const spd_value_map_t spd_lp4_nbytes_total_map[] = {
35 	{ SPD_LP4_NBYTES_TOTAL_UNDEF, 0, true },
36 	{ SPD_LP4_NBYTES_TOTAL_256, 256, false },
37 	{ SPD_LP4_NBYTES_TOTAL_512, 512, false }
38 };
39 
40 static void
spd_parse_lp4_nbytes(spd_info_t * si,uint32_t off,uint32_t len,const char * key)41 spd_parse_lp4_nbytes(spd_info_t *si, uint32_t off, uint32_t len,
42     const char *key)
43 {
44 	const uint8_t data = si->si_data[off];
45 	const uint8_t used = SPD_LP4_NBYTES_USED(data);
46 	const uint8_t total = SPD_LP4_NBYTES_TOTAL(data);
47 
48 	spd_insert_map(si, SPD_KEY_NBYTES_USED, used, spd_lp4_nbytes_used_map,
49 	    ARRAY_SIZE(spd_lp4_nbytes_used_map));
50 	spd_insert_map(si, SPD_KEY_NBYTES_TOTAL, total,
51 	    spd_lp4_nbytes_total_map, ARRAY_SIZE(spd_lp4_nbytes_total_map));
52 
53 	/*
54 	 * Like with DDR4 there is no specific way to determine the type. We
55 	 * take our best guess based upon the size. A 256 byte EEPROM is most
56 	 * likely the EE1002 spec and the 512 byte EEPROM is the EE1004 as those
57 	 * are the defended sized.
58 	 */
59 	spd_upsert_flag(si, SPD_KEY_DEVS, SPD_DEVICE_SPD);
60 	if (total == SPD_LP4_NBYTES_TOTAL_256) {
61 		spd_nvl_insert_u32(si, SPD_KEY_DEV_SPD_TYPE, SPD_SPD_T_EE1002);
62 	} else if (total == SPD_LP4_NBYTES_TOTAL_512) {
63 		spd_nvl_insert_u32(si, SPD_KEY_DEV_SPD_TYPE, SPD_SPD_T_EE1004);
64 	}
65 }
66 
67 /*
68  * Like DDR4 the value of zero is defined for extensions; however, there are no
69  * defined type extensions. As such we don't check for it.
70  */
71 static const spd_value_map_t spd_lp4_mod_type_map[] = {
72 	{ SPD_LP4_MOD_TYPE_TYPE_LPDIMM, SPD_MOD_TYPE_LPDIMM, false },
73 	{ SPD_LP4_MOD_TYPE_TYPE_SOLDER, SPD_MOD_TYPE_SOLDER, false }
74 };
75 
76 static const spd_value_map_t spd_lp4_mod_is_hybrid_map[] = {
77 	{ 0, SPD_MOD_NOT_HYBRID, false },
78 	{ 1, SPD_MOD_HYBRID_NVDIMMM, false }
79 };
80 
81 static void
spd_parse_lp4_mod_type(spd_info_t * si,uint32_t off,uint32_t len,const char * key)82 spd_parse_lp4_mod_type(spd_info_t *si, uint32_t off, uint32_t len,
83     const char *key)
84 {
85 	const uint8_t data = si->si_data[off];
86 	const uint8_t type = SPD_LP4_MOD_TYPE_TYPE(data);
87 	const uint8_t is_hyb = SPD_LP4_MOD_TYPE_ISHYBRID(data);
88 
89 	spd_insert_map(si, SPD_KEY_MOD_HYBRID_TYPE, is_hyb,
90 	    spd_lp4_mod_is_hybrid_map, ARRAY_SIZE(spd_lp4_mod_is_hybrid_map));
91 
92 	spd_insert_map(si, SPD_KEY_MOD_TYPE, type, spd_lp4_mod_type_map,
93 	    ARRAY_SIZE(spd_lp4_mod_type_map));
94 }
95 
96 static const spd_value_map64_t spd_lp4_density_map[] = {
97 	{ SPD_LP4_DENSITY_DENSITY_1Gb, 1ULL * 1024ULL * 1024ULL * 1024ULL,
98 	    false },
99 	{ SPD_LP4_DENSITY_DENSITY_2Gb, 2ULL * 1024ULL * 1024ULL * 1024ULL,
100 	    false },
101 	{ SPD_LP4_DENSITY_DENSITY_4Gb, 4ULL * 1024ULL * 1024ULL * 1024ULL,
102 	    false },
103 	{ SPD_LP4_DENSITY_DENSITY_8Gb, 8ULL * 1024ULL * 1024ULL * 1024ULL,
104 	    false },
105 	{ SPD_LP4_DENSITY_DENSITY_16Gb, 16ULL * 1024ULL * 1024ULL * 1024ULL,
106 	    false },
107 	{ SPD_LP4_DENSITY_DENSITY_32Gb, 32ULL * 1024ULL * 1024ULL * 1024ULL,
108 	    false },
109 	{ SPD_LP4_DENSITY_DENSITY_12Gb, 12ULL * 1024ULL * 1024ULL * 1024ULL,
110 	    false },
111 	{ SPD_LP4_DENSITY_DENSITY_24Gb, 24ULL * 1024ULL * 1024ULL * 1024ULL,
112 	    false },
113 	{ SPD_LP4_DENSITY_DENSITY_3Gb, 3ULL * 1024ULL * 1024ULL * 1024ULL,
114 	    false },
115 	{ SPD_LP4_DENSITY_DENSITY_6Gb, 64ULL * 1024ULL * 1024ULL * 1024ULL,
116 	    false },
117 	{ SPD_LP4_DENSITY_DENSITY_18Gb, 18ULL * 1024ULL * 1024ULL * 1024ULL,
118 	    false },
119 };
120 
121 static const spd_value_range_t spd_lp4_nbgrp_range = {
122 	.svr_max = SPD_LP4_DENSITY_NBG_BITS_MAX
123 };
124 
125 static const spd_value_range_t spd_lp4_nba_range = {
126 	.svr_max = SPD_LP4_DENSITY_NBA_BITS_MAX,
127 	.svr_base = SPD_LP4_DENSITY_NBA_BITS_BASE
128 };
129 
130 static void
spd_parse_lp4_density(spd_info_t * si,uint32_t off,uint32_t len,const char * key)131 spd_parse_lp4_density(spd_info_t *si, uint32_t off, uint32_t len,
132     const char *key)
133 {
134 	const uint8_t data = si->si_data[off];
135 	const uint8_t nbg = SPD_LP4_DENSITY_NBG_BITS(data);
136 	const uint8_t nbank = SPD_LP4_DENSITY_NBA_BITS(data);
137 	const uint8_t dens = SPD_LP4_DENSITY_DENSITY(data);
138 
139 	spd_insert_range(si, SPD_KEY_NBGRP_BITS, nbg, &spd_lp4_nbgrp_range);
140 	spd_insert_range(si, SPD_KEY_NBANK_BITS, nbank, &spd_lp4_nba_range);
141 	spd_insert_map64(si, SPD_KEY_DIE_SIZE, dens, spd_lp4_density_map,
142 	    ARRAY_SIZE(spd_lp4_density_map));
143 }
144 
145 static const spd_value_range_t spd_lp4_nrow_range = {
146 	.svr_max = SPD_LP4_ADDR_NROWS_MAX,
147 	.svr_base = SPD_LP4_ADDR_NROWS_BASE
148 };
149 
150 static const spd_value_range_t spd_lp4_ncol_range = {
151 	.svr_max = SPD_LP4_ADDR_NCOLS_MAX,
152 	.svr_base = SPD_LP4_ADDR_NCOLS_BASE
153 };
154 
155 static void
spd_parse_lp4_addr(spd_info_t * si,uint32_t off,uint32_t len,const char * key)156 spd_parse_lp4_addr(spd_info_t *si, uint32_t off, uint32_t len,
157     const char *key)
158 {
159 	const uint8_t data = si->si_data[off];
160 	const uint8_t nrows = SPD_LP4_ADDR_NROWS(data);
161 	const uint8_t ncols = SPD_LP4_ADDR_NCOLS(data);
162 
163 	spd_insert_range(si, SPD_KEY_NROW_BITS, nrows, &spd_lp4_nrow_range);
164 	spd_insert_range(si, SPD_KEY_NCOL_BITS, ncols, &spd_lp4_ncol_range);
165 }
166 
167 static const spd_value_range_t spd_lp4_ndie_range = {
168 	.svr_base = SPD_LP4_PKG_DIE_CNT_BASE
169 };
170 
171 static const spd_value_range_t spd_lp4_nchan_range = {
172 	.svr_max = SPD_LP4_PKG_NCHAN_MAX,
173 	.svr_exp = true
174 };
175 
176 static void
spd_parse_lp4_pkg(spd_info_t * si,uint32_t off,uint32_t len,const char * key)177 spd_parse_lp4_pkg(spd_info_t *si, uint32_t off, uint32_t len,
178     const char *key)
179 {
180 	const uint8_t data = si->si_data[off];
181 	const uint8_t ndie = SPD_LP4_PKG_DIE_CNT(data);
182 	const uint8_t nchan = SPD_LP4_PKG_NCHAN(data);
183 
184 
185 	if (SPD_LP4_PKG_TYPE(data) == SPD_LP4_PKG_TYPE_NOT) {
186 		spd_nvl_insert_key(si, SPD_KEY_PKG_NOT_MONO);
187 	}
188 
189 	spd_insert_range(si, SPD_KEY_PKG_NDIE, ndie, &spd_lp4_ndie_range);
190 	spd_insert_range(si, SPD_KEY_DRAM_NCHAN, nchan, &spd_lp4_nchan_range);
191 }
192 
193 static const spd_value_map_t spd_lp4_maw_map[] = {
194 	{ SPD_LP4_OPT_FEAT_MAW_8192X, 8192, false },
195 	{ SPD_LP4_OPT_FEAT_MAW_4096X, 4096, false },
196 	{ SPD_LP4_OPT_FEAT_MAW_2048X, 2048, false }
197 };
198 
199 static const spd_value_map_t spd_lp4_mac_map[] = {
200 	{ SPD_LP4_OPT_FEAT_MAC_UNTESTED, 0, true},
201 	{ SPD_LP4_OPT_FEAT_MAC_700K, 700000, false },
202 	{ SPD_LP4_OPT_FEAT_MAC_600K, 600000, false },
203 	{ SPD_LP4_OPT_FEAT_MAC_500K, 500000, false },
204 	{ SPD_LP4_OPT_FEAT_MAC_400K, 400000, false },
205 	{ SPD_LP4_OPT_FEAT_MAC_300K, 300000, false },
206 	{ SPD_LP4_OPT_FEAT_MAC_200K, 200000, false },
207 	{ SPD_LP4_OPT_FEAT_MAC_UNLIMITED, SPD_KEY_MAC_UNLIMITED, false }
208 };
209 
210 static void
spd_parse_lp4_feat(spd_info_t * si,uint32_t off,uint32_t len,const char * key)211 spd_parse_lp4_feat(spd_info_t *si, uint32_t off, uint32_t len,
212     const char *key)
213 {
214 	const uint8_t data = si->si_data[off];
215 	const uint8_t maw = SPD_DDR4_OPT_FEAT_MAW(data);
216 	const uint8_t mac = SPD_DDR4_OPT_FEAT_MAC(data);
217 
218 	spd_insert_map(si, SPD_KEY_MAW, maw, spd_lp4_maw_map,
219 	    ARRAY_SIZE(spd_lp4_maw_map));
220 	spd_insert_map(si, SPD_KEY_MAC, mac, spd_lp4_mac_map,
221 	    ARRAY_SIZE(spd_lp4_mac_map));
222 }
223 
224 static void
spd_parse_lp4_feat2(spd_info_t * si,uint32_t off,uint32_t len,const char * key)225 spd_parse_lp4_feat2(spd_info_t *si, uint32_t off, uint32_t len,
226     const char *key)
227 {
228 	const uint8_t data = si->si_data[off];
229 	const uint8_t ppr_sup = SPD_DDR4_OPT_FEAT2_PPR(data);
230 	spd_ppr_flags_t flags = 0;
231 
232 	switch (ppr_sup) {
233 	case SPD_LP4_OPT_FEAT2_PPR_1RPBG:
234 		spd_nvl_insert_u32(si, SPD_KEY_PPR_GRAN,
235 		    SPD_PPR_GRAN_BANK_GROUP);
236 		flags |= SPD_PPR_F_HARD_PPR;
237 		break;
238 	case SPD_LP4_OPT_FEAT2_PPR_NOTSUP:
239 		/*
240 		 * No PPR, nothing to do.
241 		 */
242 		return;
243 	default:
244 		/*
245 		 * Unknown PPR value.
246 		 */
247 		spd_nvl_err(si, SPD_KEY_PPR, SPD_ERROR_NO_XLATE,
248 		    "encountered unknown value: 0x%x", ppr_sup);
249 		return;
250 	}
251 
252 	if (SPD_LP4_OPT_FEAT2_SOFT_PPR(data))
253 		flags |= SPD_PPR_F_SOFT_PPR;
254 	spd_nvl_insert_u32(si, SPD_KEY_PPR, flags);
255 }
256 
257 static const spd_value_range_t spd_lp4_nrank_range = {
258 	.svr_max = SPD_LP4_MOD_ORG_NPKG_RANK_MAX,
259 	.svr_base = SPD_LP4_MOD_ORG_NPKG_RANK_BASE
260 };
261 
262 static const spd_value_range_t spd_lp4_width_range = {
263 	.svr_max = SPD_LP4_MOD_ORG_WIDTH_MAX,
264 	.svr_base = SPD_LP4_MOD_ORG_WIDTH_BASE,
265 	.svr_exp = true
266 };
267 
268 static void
spd_parse_lp4_mod_org(spd_info_t * si,uint32_t off,uint32_t len,const char * key)269 spd_parse_lp4_mod_org(spd_info_t *si, uint32_t off, uint32_t len,
270     const char *key)
271 {
272 	const uint8_t data = si->si_data[off];
273 	const uint8_t byte = SPD_LP4_MOD_ORG_IDENT(data);
274 	const uint8_t nrank = SPD_LP4_MOD_ORG_NPKG_RANK(data);
275 	const uint8_t width = SPD_LP4_MOD_ORG_WIDTH(data);
276 
277 	if (byte == SPD_LP4_MOD_ORG_IDENT_BYTE) {
278 		spd_nvl_insert_key(si, SPD_KEY_LP_BYTE_MODE);
279 	}
280 
281 	spd_insert_range(si, SPD_KEY_NRANKS, nrank, &spd_lp4_nrank_range);
282 	spd_insert_range(si, SPD_KEY_DRAM_WIDTH, width, &spd_lp4_width_range);
283 
284 }
285 
286 static const spd_value_map_t spd_lp4_chan_map[] = {
287 	{ SPD_LP4_BUS_WIDTH_NCHAN_1ch, 1, false },
288 	{ SPD_LP4_BUS_WIDTH_NCHAN_2ch, 2, false },
289 	{ SPD_LP4_BUS_WIDTH_NCHAN_3ch, 3, false },
290 	{ SPD_LP4_BUS_WIDTH_NCHAN_4ch, 4, false },
291 	{ SPD_LP4_BUS_WIDTH_NCHAN_8ch, 8, false }
292 };
293 
294 static const spd_value_range_t spd_lp4_chan_width_range = {
295 	.svr_base = SPD_LP4_BUS_WIDTH_PRI_BASE,
296 	.svr_max = SPD_LP4_BUS_WIDTH_PRI_MAX,
297 	.svr_exp = true
298 };
299 
300 static void
spd_parse_lp4_bus_width(spd_info_t * si,uint32_t off,uint32_t len,const char * key)301 spd_parse_lp4_bus_width(spd_info_t *si, uint32_t off, uint32_t len,
302     const char *key)
303 {
304 	const uint8_t data = si->si_data[off];
305 	const uint8_t nchan = SPD_LP4_BUS_WIDTH_NCHAN(data);
306 	const uint8_t ext = SPD_LP4_BUS_WIDTH_EXT(data);
307 	const uint8_t width = SPD_LP4_BUS_WIDTH_PRI(data);
308 
309 	spd_insert_map(si, SPD_KEY_NSUBCHAN, nchan, spd_lp4_chan_map,
310 	    ARRAY_SIZE(spd_lp4_chan_map));
311 
312 	if (ext != SPD_LP4_BUS_WIDTH_EXT_NONE) {
313 		spd_nvl_err(si, SPD_KEY_ECC_WIDTH, SPD_ERROR_NO_XLATE,
314 		    "encountered invalid bus width extension: 0x%x", ext);
315 	} else {
316 		spd_nvl_insert_u32(si, SPD_KEY_ECC_WIDTH, 0);
317 	}
318 
319 	spd_insert_range(si, SPD_KEY_DATA_WIDTH, width,
320 	    &spd_lp4_chan_width_range);
321 }
322 
323 static void
spd_parse_lp4_therm(spd_info_t * si,uint32_t off,uint32_t len,const char * key)324 spd_parse_lp4_therm(spd_info_t *si, uint32_t off, uint32_t len,
325     const char *key)
326 {
327 	const uint8_t data = si->si_data[off];
328 
329 	if (SPD_LP4_MOD_THERM_PRES(data) != 0)
330 		spd_upsert_flag(si, key, SPD_DEVICE_TEMP_1);
331 	/*
332 	 * Like DDR4, LPDDR3/4 only define that this must be TSE2004av
333 	 * compliant.
334 	 */
335 	spd_nvl_insert_u32(si, SPD_KEY_DEV_TEMP_TYPE, SPD_TEMP_T_TSE2004av);
336 }
337 
338 static const spd_value_range_t spd_lp4_dsm_range = {
339 	.svr_max = SPD_LP4_SIGLOAD1_DSM_LOAD_MAX,
340 	.svr_exp = true
341 };
342 
343 static const spd_value_range_t spd_lp4_cac_range = {
344 	.svr_max = SPD_LP4_SIGLOAD1_CAC_LOAD_MAX,
345 	.svr_exp = true
346 };
347 
348 static const spd_value_range_t spd_lp4_cs_range = {
349 	.svr_max = SPD_LP4_SIGLOAD1_CS_LOAD_MAX,
350 	.svr_exp = true
351 };
352 
353 static void
spd_parse_lp4_sigload(spd_info_t * si,uint32_t off,uint32_t len,const char * key)354 spd_parse_lp4_sigload(spd_info_t *si, uint32_t off, uint32_t len,
355     const char *key)
356 {
357 	const uint8_t data = si->si_data[off];
358 	const uint8_t dsm = SPD_LP5_SIGLOAD1_DSM_LOAD(data);
359 	const uint8_t cac = SPD_LP5_SIGLOAD1_CAC_LOAD(data);
360 	const uint8_t cs = SPD_LP5_SIGLOAD1_CS_LOAD(data);
361 
362 	spd_insert_range(si, SPD_KEY_LP_LOAD_DSM, dsm, &spd_lp4_dsm_range);
363 	spd_insert_range(si, SPD_KEY_LP_LOAD_CAC, cac, &spd_lp4_cac_range);
364 	spd_insert_range(si, SPD_KEY_LP_LOAD_CS, cs, &spd_lp4_cs_range);
365 }
366 
367 static const spd_value_map_t spd_lp4_ts_mtb[] = {
368 	{ SPD_LP4_TIMEBASE_MTB_125ps, SPD_LP4_MTB_PS, false }
369 };
370 
371 static const spd_value_map_t spd_lp4_ts_ftb[] = {
372 	{ SPD_LP4_TIMEBASE_FTB_1ps, SPD_LP4_FTB_PS, false }
373 };
374 
375 static void
spd_parse_lp4_timebase(spd_info_t * si,uint32_t off,uint32_t len,const char * key)376 spd_parse_lp4_timebase(spd_info_t *si, uint32_t off, uint32_t len,
377     const char *key)
378 {
379 	const uint8_t data = si->si_data[off];
380 	const uint8_t mtb = SPD_LP4_TIMEBASE_MTB(data);
381 	const uint8_t ftb = SPD_LP4_TIMEBASE_FTB(data);
382 
383 	spd_insert_map(si, SPD_KEY_MTB, mtb, spd_lp4_ts_mtb,
384 	    ARRAY_SIZE(spd_lp4_ts_mtb));
385 	spd_insert_map(si, SPD_KEY_FTB, ftb, spd_lp4_ts_ftb,
386 	    ARRAY_SIZE(spd_lp4_ts_ftb));
387 }
388 
389 /*
390  * The first byte of CAS values is non-uniform. The second byte onwards begins
391  * at CL=16 and each bit indicates a CL two apart. We use this array of all CAS
392  * values as most of them aren't actually defined by the spec.
393  */
394 static const uint32_t spd_lp4_cas_map[32] = {
395 	[0] = 3,
396 	[1] = 6,
397 	[2] = 8,
398 	[3] = 9,
399 	[4] = 10,
400 	[5] = 11,
401 	[6] = 12,
402 	[7] = 14,
403 	[8] = 16,
404 	[9] = 18,
405 	[10] = 20,
406 	[11] = 22,
407 	[12] = 24,
408 	[13] = 26,
409 	[14] = 28,
410 	[15] = 30,
411 	[16] = 32,
412 	[18] = 36,
413 	[20] = 40,
414 	[22] = 44
415 };
416 
417 static void
spd_parse_lp4_cas(spd_info_t * si,uint32_t off,uint32_t len,const char * key)418 spd_parse_lp4_cas(spd_info_t *si, uint32_t off, uint32_t len,
419     const char *key)
420 {
421 	uint32_t cas[32] = { 0 };
422 	uint_t ncas = 0;
423 
424 	ASSERT3U(len, ==, 4);
425 	for (uint32_t byte = 0; byte < MIN(len, 4); byte++) {
426 		uint32_t data = si->si_data[off + byte];
427 		uint32_t nbits = NBBY;
428 
429 		for (uint32_t i = 0; i < nbits; i++) {
430 			if (bitx8(data, i, i) == 1) {
431 				uint8_t pos = i + byte * NBBY;
432 				if (spd_lp4_cas_map[pos] != 0) {
433 					cas[ncas] = spd_lp4_cas_map[pos];
434 					ncas++;
435 				} else {
436 					spd_nvl_err(si, key, SPD_ERROR_BAD_DATA,
437 					    "invalid CAS byte %u/bit %u found",
438 					    byte, i);
439 					return;
440 				}
441 			}
442 		}
443 	}
444 
445 	spd_nvl_insert_u32_array(si, key, cas, ncas);
446 }
447 
448 static void
spd_parse_lp4_rwlat(spd_info_t * si,uint32_t off,uint32_t len,const char * key)449 spd_parse_lp4_rwlat(spd_info_t *si, uint32_t off, uint32_t len,
450     const char *key)
451 {
452 	const uint8_t data = si->si_data[off];
453 	const uint8_t wr = SPD_LP4_RWLAT_WRITE(data);
454 	const uint8_t rd = SPD_LP4_RWLAT_READ(data);
455 	spd_lp_rwlat_t rwlat = 0;
456 
457 	switch (wr) {
458 	case SPD_LP4_RWLAT_WRITE_A:
459 		rwlat |= SPD_LP_RWLAT_WRITE_A;
460 		break;
461 	case SPD_LP4_RWLAT_WRITE_B:
462 		rwlat |= SPD_LP_RWLAT_WRITE_B;
463 		break;
464 	default:
465 		spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "unknown write "
466 		    "latency set value: 0x%x", wr);
467 		return;
468 	}
469 
470 	switch (rd) {
471 	case SPD_LP4_RWLAT_DBIRD_DIS:
472 		break;
473 	case SPD_LP4_RWLAT_DBIRD_EN:
474 		rwlat |= SPD_LP_RWLAT_DBIRD_EN;
475 		break;
476 	default:
477 		spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "unknown read "
478 		    "latency mode value: 0x%x", rd);
479 		return;
480 	}
481 
482 	spd_nvl_insert_u32(si, key, rwlat);
483 }
484 
485 static const spd_parse_t spd_lp4_base[] = {
486 	{ .sp_off = SPD_LP4_NBYTES, .sp_parse = spd_parse_lp4_nbytes },
487 	{ .sp_off = SPD_LP4_SPD_REV, .sp_parse = spd_parse_rev },
488 	/*
489 	 * We have previously validated that the DRAM type is something that we
490 	 * understand. We pass through the raw enum to users here.
491 	 */
492 	{ .sp_off = SPD_LP4_DRAM_TYPE, .sp_key = SPD_KEY_DRAM_TYPE,
493 	    .sp_parse = spd_parse_raw_u8 },
494 
495 	{ .sp_off = SPD_LP4_MOD_TYPE, .sp_parse = spd_parse_lp4_mod_type },
496 	{ .sp_off = SPD_LP4_DENSITY, .sp_parse = spd_parse_lp4_density },
497 	{ .sp_off = SPD_LP4_ADDR, .sp_parse = spd_parse_lp4_addr },
498 	{ .sp_off = SPD_LP4_PKG, .sp_parse = spd_parse_lp4_pkg },
499 	{ .sp_off = SPD_LP4_OPT_FEAT, .sp_parse = spd_parse_lp4_feat },
500 	{ .sp_off = SPD_LP4_OPT_FEAT2, .sp_parse = spd_parse_lp4_feat2 },
501 	{ .sp_off = SPD_LP4_MOD_ORG, .sp_parse = spd_parse_lp4_mod_org },
502 	{ .sp_off = SPD_LP4_BUS_WIDTH, .sp_parse = spd_parse_lp4_bus_width },
503 	{ .sp_off = SPD_LP4_MOD_THERM, .sp_parse = spd_parse_lp4_therm },
504 	{ .sp_off = SPD_LP4_SIGLOAD, .sp_parse = spd_parse_lp4_sigload },
505 	{ .sp_off = SPD_LP4_TIMEBASE, .sp_parse = spd_parse_lp4_timebase },
506 	{ .sp_off = SPD_LP4_TCKAVG_MIN, .sp_key = SPD_KEY_TCKAVG_MIN,
507 	    .sp_len = SPD_LP4_TCKAVG_MIN_FINE - SPD_LP4_TCKAVG_MIN + 1,
508 	    .sp_parse = spd_parse_mtb_ftb_time_pair },
509 	{ .sp_off = SPD_LP4_TCKAVG_MAX, .sp_key = SPD_KEY_TCKAVG_MAX,
510 	    .sp_len = SPD_LP4_TCKAVG_MAX_FINE - SPD_LP4_TCKAVG_MAX + 1,
511 	    .sp_parse = spd_parse_mtb_ftb_time_pair },
512 	{ .sp_off = SPD_LP4_CAS_SUP0, .sp_key = SPD_KEY_CAS,
513 	    .sp_len = SPD_LP4_CAS_SUP3 - SPD_LP4_CAS_SUP0 + 1,
514 	    .sp_parse = spd_parse_lp4_cas },
515 	{ .sp_off = SPD_LP5_TAA_MIN, .sp_key = SPD_KEY_TAA_MIN,
516 	    .sp_len = SPD_LP5_TAA_MIN_FINE - SPD_LP5_TAA_MIN + 1,
517 	    .sp_parse = spd_parse_mtb_ftb_time_pair },
518 	{ .sp_off = SPD_LP4_RWLAT, .sp_key = SPD_KEY_LP_RWLAT,
519 	    .sp_parse = spd_parse_lp4_rwlat },
520 	{ .sp_off = SPD_LP5_TRCD_MIN, .sp_key = SPD_KEY_TRCD_MIN,
521 	    .sp_len = SPD_LP5_TRCD_MIN_FINE - SPD_LP5_TRCD_MIN + 1,
522 	    .sp_parse = spd_parse_mtb_ftb_time_pair },
523 	{ .sp_off = SPD_LP5_TRPAB_MIN, .sp_key = SPD_KEY_TRPAB_MIN,
524 	    .sp_len = SPD_LP5_TRPAB_MIN_FINE - SPD_LP5_TRPAB_MIN + 1,
525 	    .sp_parse = spd_parse_mtb_ftb_time_pair },
526 	{ .sp_off = SPD_LP5_TRPPB_MIN, .sp_key = SPD_KEY_TRPPB_MIN,
527 	    .sp_len = SPD_LP5_TRPPB_MIN_FINE - SPD_LP5_TRPPB_MIN + 1,
528 	    .sp_parse = spd_parse_mtb_ftb_time_pair },
529 	{ .sp_off = SPD_LP5_TRPPB_MIN, .sp_key = SPD_KEY_TRPPB_MIN,
530 	    .sp_len = SPD_LP5_TRPPB_MIN_FINE - SPD_LP5_TRPPB_MIN + 1,
531 	    .sp_parse = spd_parse_mtb_ftb_time_pair },
532 	{ .sp_off = SPD_LP4_TRFCAB_MIN_LO, .sp_key = SPD_KEY_TRFCAB_MIN,
533 	    .sp_len = 2, .sp_parse = spd_parse_mtb_pair },
534 	{ .sp_off = SPD_LP4_TRFCPB_MIN_LO, .sp_key = SPD_KEY_TRFCPB_MIN,
535 	    .sp_len = 2, .sp_parse = spd_parse_mtb_pair },
536 	{ .sp_off = SPD_LP4_MAP_DQ0, .sp_key = SPD_KEY_DDR4_MAP_DQ0,
537 	    .sp_parse = spd_parse_ddr4_nib_map },
538 	{ .sp_off = SPD_LP4_MAP_DQ4, .sp_key = SPD_KEY_DDR4_MAP_DQ4,
539 	    .sp_parse = spd_parse_ddr4_nib_map },
540 	{ .sp_off = SPD_LP4_MAP_DQ8, .sp_key = SPD_KEY_DDR4_MAP_DQ8,
541 	    .sp_parse = spd_parse_ddr4_nib_map },
542 	{ .sp_off = SPD_LP4_MAP_DQ12, .sp_key = SPD_KEY_DDR4_MAP_DQ12,
543 	    .sp_parse = spd_parse_ddr4_nib_map },
544 	{ .sp_off = SPD_LP4_MAP_DQ16, .sp_key = SPD_KEY_DDR4_MAP_DQ16,
545 	    .sp_parse = spd_parse_ddr4_nib_map },
546 	{ .sp_off = SPD_LP4_MAP_DQ20, .sp_key = SPD_KEY_DDR4_MAP_DQ20,
547 	    .sp_parse = spd_parse_ddr4_nib_map },
548 	{ .sp_off = SPD_LP4_MAP_DQ24, .sp_key = SPD_KEY_DDR4_MAP_DQ24,
549 	    .sp_parse = spd_parse_ddr4_nib_map },
550 	{ .sp_off = SPD_LP4_MAP_DQ28, .sp_key = SPD_KEY_DDR4_MAP_DQ28,
551 	    .sp_parse = spd_parse_ddr4_nib_map },
552 	{ .sp_off = SPD_LP4_MAP_CB0, .sp_key = SPD_KEY_DDR4_MAP_CB0,
553 	    .sp_parse = spd_parse_ddr4_nib_map },
554 	{ .sp_off = SPD_LP4_MAP_CB4, .sp_key = SPD_KEY_DDR4_MAP_CB4,
555 	    .sp_parse = spd_parse_ddr4_nib_map },
556 	{ .sp_off = SPD_LP4_MAP_DQ32, .sp_key = SPD_KEY_DDR4_MAP_DQ32,
557 	    .sp_parse = spd_parse_ddr4_nib_map },
558 	{ .sp_off = SPD_LP4_MAP_DQ36, .sp_key = SPD_KEY_DDR4_MAP_DQ36,
559 	    .sp_parse = spd_parse_ddr4_nib_map },
560 	{ .sp_off = SPD_LP4_MAP_DQ40, .sp_key = SPD_KEY_DDR4_MAP_DQ40,
561 	    .sp_parse = spd_parse_ddr4_nib_map },
562 	{ .sp_off = SPD_LP4_MAP_DQ44, .sp_key = SPD_KEY_DDR4_MAP_DQ44,
563 	    .sp_parse = spd_parse_ddr4_nib_map },
564 	{ .sp_off = SPD_LP4_MAP_DQ48, .sp_key = SPD_KEY_DDR4_MAP_DQ48,
565 	    .sp_parse = spd_parse_ddr4_nib_map },
566 	{ .sp_off = SPD_LP4_MAP_DQ52, .sp_key = SPD_KEY_DDR4_MAP_DQ52,
567 	    .sp_parse = spd_parse_ddr4_nib_map },
568 	{ .sp_off = SPD_LP4_MAP_DQ56, .sp_key = SPD_KEY_DDR4_MAP_DQ56,
569 	    .sp_parse = spd_parse_ddr4_nib_map },
570 	{ .sp_off = SPD_LP4_MAP_DQ60, .sp_key = SPD_KEY_DDR4_MAP_DQ60,
571 	    .sp_parse = spd_parse_ddr4_nib_map },
572 	{ .sp_len = SPD_DDR4_CRC_MSB + 1, .sp_key = SPD_KEY_CRC_DDR4_BASE,
573 	    .sp_parse = spd_parse_crc },
574 	{ .sp_len = SPD_LP4_CRC_MSB + 1, .sp_key = SPD_KEY_CRC_DDR4_BASE,
575 	    .sp_parse = spd_parse_crc },
576 };
577 
578 static const spd_parse_t spd_lp4_lpdimm[] = {
579 	{ .sp_off = SPD_LP4_LPDIMM_HEIGHT, .sp_key = SPD_KEY_MOD_HEIGHT,
580 	    .sp_parse = spd_parse_height },
581 	{ .sp_off = SPD_LP4_LPDIMM_THICK, .sp_parse = spd_parse_thickness },
582 	{ .sp_off = SPD_LP4_LPDIMM_REF, .sp_parse = spd_parse_ddr4_design },
583 	{ .sp_off = SPD_LP4_BLK1_CRC_START, .sp_len = SPD_LP4_BLK1_CRC_MSB +
584 	    1 - SPD_LP4_BLK1_CRC_START, .sp_key = SPD_KEY_CRC_DDR4_BLK1,
585 	    .sp_parse = spd_parse_crc }
586 };
587 
588 static void
spd_parse_lp4_mod_specific(spd_info_t * si)589 spd_parse_lp4_mod_specific(spd_info_t *si)
590 {
591 	uint32_t type;
592 
593 	if (nvlist_lookup_uint32(si->si_nvl, SPD_KEY_MOD_TYPE, &type) != 0)
594 		return;
595 
596 	switch (type) {
597 	case SPD_MOD_TYPE_LPDIMM:
598 		spd_parse(si, spd_lp4_lpdimm, ARRAY_SIZE(spd_lp4_lpdimm));
599 		break;
600 	default:
601 		break;
602 	}
603 }
604 
605 void
spd_parse_lp4(spd_info_t * si)606 spd_parse_lp4(spd_info_t *si)
607 {
608 	if (SPD_LP4_SPD_REV_ENC(si->si_data[SPD_LP4_SPD_REV]) !=
609 	    SPD_LP4_SPD_REV_V1) {
610 		si->si_error = LIBJEDEC_SPD_UNSUP_REV;
611 		return;
612 	}
613 
614 	spd_parse(si, spd_lp4_base, ARRAY_SIZE(spd_lp4_base));
615 	spd_parse_lp4_mod_specific(si);
616 	spd_parse_ddr4_mfg(si);
617 }
618