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 * LPDDR5/x-specific SPD processing logic. For an overview of the processing 18 * design please see libjedec_spd.c. LPDDR5 has a similar design to DDR5 and 19 * even uses the same common module, manufacturing, and module-specific data. 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_lp5_nbytes_total_map[] = { 27 { SPD_DDR5_NBYTES_TOTAL_UNDEF, 0, true }, 28 { SPD_DDR5_NBYTES_TOTAL_256, 256, false }, 29 { SPD_DDR5_NBYTES_TOTAL_512, 512, false }, 30 { SPD_DDR5_NBYTES_TOTAL_1024, 1024, false }, 31 { SPD_DDR5_NBYTES_TOTAL_2048, 2048, false } 32 }; 33 34 static void 35 spd_parse_lp5_nbytes(spd_info_t *si, uint32_t off, uint32_t len, 36 const char *key) 37 { 38 const uint8_t data = si->si_data[off]; 39 const uint8_t total = SPD_LP5_NBYTES_TOTAL(data); 40 uint8_t beta = SPD_LP5_NBYTES_BETA(data); 41 beta = bitset8(beta, 4, 4, SPD_LP5_NBYTES_BETAHI(data)); 42 43 spd_nvl_insert_u32(si, SPD_KEY_BETA, beta); 44 spd_insert_map(si, SPD_KEY_NBYTES_TOTAL, total, 45 spd_lp5_nbytes_total_map, ARRAY_SIZE(spd_lp5_nbytes_total_map)); 46 } 47 48 static const spd_value_map64_t spd_lp5_density_map[] = { 49 { SPD_LP5_DENSITY_DENSITY_1Gb, 1ULL * 1024ULL * 1024ULL * 1024ULL, 50 false }, 51 { SPD_LP5_DENSITY_DENSITY_2Gb, 2ULL * 1024ULL * 1024ULL * 1024ULL, 52 false }, 53 { SPD_LP5_DENSITY_DENSITY_4Gb, 4ULL * 1024ULL * 1024ULL * 1024ULL, 54 false }, 55 { SPD_LP5_DENSITY_DENSITY_8Gb, 8ULL * 1024ULL * 1024ULL * 1024ULL, 56 false }, 57 { SPD_LP5_DENSITY_DENSITY_16Gb, 16ULL * 1024ULL * 1024ULL * 1024ULL, 58 false }, 59 { SPD_LP5_DENSITY_DENSITY_32Gb, 32ULL * 1024ULL * 1024ULL * 1024ULL, 60 false }, 61 { SPD_LP5_DENSITY_DENSITY_12Gb, 12ULL * 1024ULL * 1024ULL * 1024ULL, 62 false }, 63 { SPD_LP5_DENSITY_DENSITY_24Gb, 24ULL * 1024ULL * 1024ULL * 1024ULL, 64 false }, 65 { SPD_LP5_DENSITY_DENSITY_3Gb, 3ULL * 1024ULL * 1024ULL * 1024ULL, 66 false }, 67 { SPD_LP5_DENSITY_DENSITY_6Gb, 6ULL * 1024ULL * 1024ULL * 1024ULL, 68 false }, 69 }; 70 71 static const spd_value_range_t spd_lp5_nbg_range = { 72 .svr_max = SPD_LP5_DENSITY_NBG_BITS_MAX 73 }; 74 75 static const spd_value_range_t spd_lp5_nba_range = { 76 .svr_max = SPD_LP5_DENSITY_NBA_BITS_MAX, 77 .svr_base = SPD_LP5_DENSITY_NBA_BITS_BASE 78 }; 79 80 static void 81 spd_parse_lp5_density(spd_info_t *si, uint32_t off, uint32_t len, 82 const char *key) 83 { 84 const uint8_t data = si->si_data[off]; 85 const uint8_t nbg = SPD_LP5_DENSITY_NBG_BITS(data); 86 const uint8_t nba = SPD_LP5_DENSITY_NBA_BITS(data); 87 const uint8_t dens = SPD_LP5_DENSITY_DENSITY(data); 88 89 spd_insert_range(si, SPD_KEY_NBGRP_BITS, nbg, &spd_lp5_nbg_range); 90 spd_insert_range(si, SPD_KEY_NBANK_BITS, nba, &spd_lp5_nba_range); 91 spd_insert_map64(si, SPD_KEY_DIE_SIZE, dens, spd_lp5_density_map, 92 ARRAY_SIZE(spd_lp5_density_map)); 93 } 94 95 static const spd_value_map_t spd_lp5_ncol_map[] = { 96 { SPD_LP5_ADDRESS_BCOL_3BA6C, 6, false }, 97 { SPD_LP5_ADDRESS_BCOL_4BA6C, 6, false } 98 }; 99 100 static const spd_value_range_t spd_lp5_nrow_range = { 101 .svr_max = SPD_LP5_ADDRESS_NROW_MAX, 102 .svr_base = SPD_LP5_ADDRESS_NROW_BASE 103 }; 104 105 static void 106 spd_parse_lp5_address(spd_info_t *si, uint32_t off, uint32_t len, 107 const char *key) 108 { 109 const uint8_t data = si->si_data[off]; 110 const uint8_t nrow = SPD_LP5_ADDRESS_NROWS(data); 111 const uint8_t bcol = SPD_LP5_ADDRESS_BCOL(data); 112 113 spd_insert_range(si, SPD_KEY_NROW_BITS, nrow, &spd_lp5_nrow_range); 114 spd_insert_map(si, SPD_KEY_NCOL_BITS, bcol, spd_lp5_ncol_map, 115 ARRAY_SIZE(spd_lp5_ncol_map)); 116 } 117 118 static const spd_value_map_t spd_lp5_ndie_map[] = { 119 { SPD_LP5_DIE_CNT_1, 1, false }, 120 { SPD_LP5_DIE_CNT_2, 2, false }, 121 { SPD_LP5_DIE_CNT_3, 3, false }, 122 { SPD_LP5_DIE_CNT_4, 4, false }, 123 { SPD_LP5_DIE_CNT_5, 5, false }, 124 { SPD_LP5_DIE_CNT_6, 6, false }, 125 { SPD_LP5_DIE_CNT_16, 16, false }, 126 { SPD_LP5_DIE_CNT_8, 8, false } 127 }; 128 129 /* 130 * To insert the total number of DQs we need the die width which comes later. 131 * Similarly, the signal loading index comes into play for a later word. As such 132 * we process that later as well and therefore the only thing we process here 133 * are the total number of dies. 134 */ 135 static void 136 spd_parse_lp5_pkg(spd_info_t *si, uint32_t off, uint32_t len, 137 const char *key) 138 { 139 const uint8_t data = si->si_data[off]; 140 const uint8_t ndie = SPD_LP5_PKG_DIE_CNT(data); 141 142 if (SPD_LP5_PKG_TYPE(data) == SPD_LP5_PKG_TYPE_NOT) { 143 spd_nvl_insert_key(si, SPD_KEY_PKG_NOT_MONO); 144 } 145 146 spd_insert_map(si, SPD_KEY_PKG_NDIE, ndie, spd_lp5_ndie_map, 147 ARRAY_SIZE(spd_lp5_ndie_map)); 148 } 149 150 static void 151 spd_parse_lp5_opt_feat(spd_info_t *si, uint32_t off, uint32_t len, 152 const char *key) 153 { 154 const uint8_t data = si->si_data[off]; 155 const uint8_t ppr_sup = SPD_LP5_OPT_FEAT_PPR(data); 156 spd_ppr_flags_t flags = 0; 157 158 switch (ppr_sup) { 159 case SPD_LP5_OPT_FEAT_PPR_SUP: 160 flags |= SPD_PPR_F_HARD_PPR; 161 break; 162 case SPD_LP5_OPT_FEAT_PPR_NOTSUP: 163 break; 164 default: 165 spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, 166 "encountered unknown value: 0x%x", ppr_sup); 167 return; 168 } 169 170 if (SPD_LP5_OPT_FEAT_SOFT_PPR(data) != 0) { 171 flags |= SPD_PPR_F_SOFT_PPR; 172 } 173 174 if (flags != 0) { 175 spd_nvl_insert_u32(si, key, flags); 176 } 177 } 178 179 static const spd_value_range_t spd_lp5_nrank_range = { 180 .svr_max = SPD_LP5_MOD_ORG_RANK_MAX, 181 .svr_base = SPD_LP5_MOD_ORG_RANK_BASE 182 }; 183 184 static const spd_value_range_t spd_lp5_width_range = { 185 .svr_max = SPD_LP5_MOD_ORG_WIDTH_MAX, 186 .svr_base = SPD_LP5_MOD_ORG_WIDTH_BASE, 187 .svr_exp = true 188 }; 189 190 static void 191 spd_parse_lp5_mod_org(spd_info_t *si, uint32_t off, uint32_t len, 192 const char *key) 193 { 194 const uint8_t data = si->si_data[off]; 195 const uint8_t byte = SPD_LP5_MOD_ORG_IDENT(data); 196 const uint8_t nrank = SPD_LP5_MOD_ORG_RANK(data); 197 const uint8_t width = SPD_LP5_MOD_ORG_WIDTH(data); 198 199 if (byte == SPD_LP5_MOD_ORG_IDENT_BYTE) { 200 spd_nvl_insert_key(si, SPD_KEY_LP_BYTE_MODE); 201 } 202 203 spd_insert_range(si, SPD_KEY_NRANKS, nrank, &spd_lp5_nrank_range); 204 spd_insert_range(si, SPD_KEY_DRAM_WIDTH, width, &spd_lp5_width_range); 205 } 206 207 static const spd_value_map_t spd_lp5_subchan_width[] = { 208 { SP5_LP5_WIDTH_SUBCHAN_16b, 16, false }, 209 { SP5_LP5_WIDTH_SUBCHAN_32b, 16, false } 210 }; 211 212 /* 213 * While this is nominally duplicative of the common memory channel 214 * organization, we implement it here anyways just in case. 215 */ 216 static void 217 spd_parse_lp5_width(spd_info_t *si, uint32_t off, uint32_t len, 218 const char *key) 219 { 220 const uint8_t data = si->si_data[off]; 221 const uint8_t scw = SPD_LP5_WIDTH_SUBCHAN(data); 222 223 spd_insert_map(si, key, scw, spd_lp5_subchan_width, 224 ARRAY_SIZE(spd_lp5_subchan_width)); 225 } 226 227 static const spd_value_range_t spd_lp5_dsm_range = { 228 .svr_max = SPD_LP5_SIGLOAD1_DSM_LOAD_MAX, 229 .svr_exp = true 230 }; 231 232 static const spd_value_range_t spd_lp5_cac_range = { 233 .svr_max = SPD_LP5_SIGLOAD1_CAC_LOAD_MAX, 234 .svr_exp = true 235 }; 236 237 static const spd_value_range_t spd_lp5_cs_range = { 238 .svr_max = SPD_LP5_SIGLOAD1_CS_LOAD_MAX, 239 .svr_exp = true 240 }; 241 242 static void 243 spd_parse_lp5_sigload(spd_info_t *si, uint32_t off, uint32_t len, 244 const char *key) 245 { 246 const uint8_t data = si->si_data[off]; 247 const uint8_t dsm = SPD_LP5_SIGLOAD1_DSM_LOAD(data); 248 const uint8_t cac = SPD_LP5_SIGLOAD1_CAC_LOAD(data); 249 const uint8_t cs = SPD_LP5_SIGLOAD1_CS_LOAD(data); 250 251 spd_insert_range(si, SPD_KEY_LP_LOAD_DSM, dsm, &spd_lp5_dsm_range); 252 spd_insert_range(si, SPD_KEY_LP_LOAD_CAC, cac, &spd_lp5_cac_range); 253 spd_insert_range(si, SPD_KEY_LP_LOAD_CS, cs, &spd_lp5_cs_range); 254 } 255 256 static const spd_value_map_t spd_lp5_ts_mtb[] = { 257 { SPD_LP5_TIMEBASE_MTB_125ps, SPD_LP5_MTB_PS, false } 258 }; 259 260 static const spd_value_map_t spd_lp5_ts_ftb[] = { 261 { SPD_LP5_TIMEBASE_FTB_1ps, SPD_LP5_FTB_PS, false } 262 }; 263 264 static void 265 spd_parse_lp5_timebase(spd_info_t *si, uint32_t off, uint32_t len, 266 const char *key) 267 { 268 const uint8_t data = si->si_data[off]; 269 const uint8_t mtb = SPD_LP5_TIMEBASE_MTB(data); 270 const uint8_t ftb = SPD_LP5_TIMEBASE_FTB(data); 271 272 spd_insert_map(si, SPD_KEY_MTB, mtb, spd_lp5_ts_mtb, 273 ARRAY_SIZE(spd_lp5_ts_mtb)); 274 spd_insert_map(si, SPD_KEY_FTB, ftb, spd_lp5_ts_ftb, 275 ARRAY_SIZE(spd_lp5_ts_ftb)); 276 } 277 278 static const spd_parse_t spd_lp5_base[] = { 279 { .sp_off = SPD_LP5_NBYTES, .sp_parse = spd_parse_lp5_nbytes }, 280 { .sp_off = SPD_LP5_SPD_REV, .sp_parse = spd_parse_rev }, 281 /* 282 * We have previously validated that the DRAM type is something that we 283 * understand. We pass through the raw enum to users here. 284 */ 285 { .sp_off = SPD_LP5_DRAM_TYPE, .sp_key = SPD_KEY_DRAM_TYPE, 286 .sp_parse = spd_parse_raw_u8 }, 287 /* 288 * DDR5 and LPDDR5 use the same values here, so we reuse the logic. 289 */ 290 { .sp_off = SPD_LP5_MOD_TYPE, .sp_parse = spd_parse_ddr5_mod_type }, 291 { .sp_off = SPD_LP5_DENSITY, .sp_parse = spd_parse_lp5_density }, 292 { .sp_off = SPD_LP5_ADDRESS, .sp_parse = spd_parse_lp5_address }, 293 { .sp_off = SPD_LP5_PKG, .sp_parse = spd_parse_lp5_pkg}, 294 { .sp_off = SPD_LP5_OPT_FEAT, .sp_key = SPD_KEY_PPR, 295 .sp_parse = spd_parse_lp5_opt_feat }, 296 { .sp_off = SPD_LP5_MOD_ORG, .sp_parse = spd_parse_lp5_mod_org }, 297 { .sp_off = SPD_LP5_WIDTH, .sp_key = SPD_KEY_DATA_WIDTH, 298 .sp_parse = spd_parse_lp5_width }, 299 { .sp_off = SPD_LP5_WIDTH, .sp_parse = spd_parse_lp5_sigload }, 300 { .sp_off = SPD_LP5_TIMEBASE, .sp_parse = spd_parse_lp5_timebase }, 301 { .sp_off = SPD_LP5_TCKAVG_MIN, .sp_key = SPD_KEY_TCKAVG_MIN, 302 .sp_len = SPD_LP5_TCKAVG_MIN_FINE - SPD_LP5_TCKAVG_MIN + 1, 303 .sp_parse = spd_parse_mtb_ftb_time_pair }, 304 { .sp_off = SPD_LP5_TCKAVG_MAX, .sp_key = SPD_KEY_TCKAVG_MAX, 305 .sp_len = SPD_LP5_TCKAVG_MAX_FINE - SPD_LP5_TCKAVG_MAX + 1, 306 .sp_parse = spd_parse_mtb_ftb_time_pair }, 307 { .sp_off = SPD_LP5_TAA_MIN, .sp_key = SPD_KEY_TAA_MIN, 308 .sp_len = SPD_LP5_TAA_MIN_FINE - SPD_LP5_TAA_MIN + 1, 309 .sp_parse = spd_parse_mtb_ftb_time_pair }, 310 { .sp_off = SPD_LP5_TRCD_MIN, .sp_key = SPD_KEY_TRCD_MIN, 311 .sp_len = SPD_LP5_TRCD_MIN_FINE - SPD_LP5_TRCD_MIN + 1, 312 .sp_parse = spd_parse_mtb_ftb_time_pair }, 313 { .sp_off = SPD_LP5_TRPAB_MIN, .sp_key = SPD_KEY_TRPAB_MIN, 314 .sp_len = SPD_LP5_TRPAB_MIN_FINE - SPD_LP5_TRPAB_MIN + 1, 315 .sp_parse = spd_parse_mtb_ftb_time_pair }, 316 { .sp_off = SPD_LP5_TRPPB_MIN, .sp_key = SPD_KEY_TRPPB_MIN, 317 .sp_len = SPD_LP5_TRPPB_MIN_FINE - SPD_LP5_TRPPB_MIN + 1, 318 .sp_parse = spd_parse_mtb_ftb_time_pair }, 319 { .sp_off = SPD_LP5_TRPPB_MIN, .sp_key = SPD_KEY_TRPPB_MIN, 320 .sp_len = SPD_LP5_TRPPB_MIN_FINE - SPD_LP5_TRPPB_MIN + 1, 321 .sp_parse = spd_parse_mtb_ftb_time_pair }, 322 { .sp_off = SPD_LP5_TRFCAB_MIN_LO, .sp_key = SPD_KEY_TRFCAB_MIN, 323 .sp_len = 2, .sp_parse = spd_parse_mtb_pair }, 324 { .sp_off = SPD_LP5_TRFCPB_MIN_LO, .sp_key = SPD_KEY_TRFCPB_MIN, 325 .sp_len = 2, .sp_parse = spd_parse_mtb_pair }, 326 }; 327 328 void 329 spd_parse_lp5(spd_info_t *si) 330 { 331 if (SPD_LP5_SPD_REV_ENC(si->si_data[SPD_LP5_SPD_REV]) != 332 SPD_LP5_SPD_REV_V1) { 333 si->si_error = LIBJEDEC_SPD_UNSUP_REV; 334 return; 335 } 336 337 spd_parse(si, spd_lp5_base, ARRAY_SIZE(spd_lp5_base)); 338 spd_parse_ddr5_common(si); 339 } 340