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
spd_parse_lp5_nbytes(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_density(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_address(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_pkg(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_opt_feat(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_mod_org(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_width(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_sigload(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5_timebase(spd_info_t * si,uint32_t off,uint32_t len,const char * key)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
spd_parse_lp5(spd_info_t * si)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