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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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