Pavona Software APIs
dif_otp_ctrl.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
5
6#include <stddef.h>
7
11
12#include "hw/top/otp_ctrl_regs.h" // Generated.
13
14/**
15 * Checks if integrity/consistency-check-related operations are locked.
16 *
17 * This is a convenience function to avoid superfluous error-checking in all the
18 * functions that can be locked out by this register.
19 *
20 * @param check_config True to check the config regwen. False to check the
21 * trigger regwen.
22 */
23static bool checks_are_locked(const dif_otp_ctrl_t *otp, bool check_config) {
24 ptrdiff_t reg_offset = check_config
25 ? OTP_CTRL_CHECK_REGWEN_REG_OFFSET
26 : OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET;
27 size_t regwen_bit =
28 check_config ? OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT
29 : OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT;
30 uint32_t locked = mmio_region_read32(otp->base_addr, reg_offset);
31 return !bitfield_bit32_read(locked, regwen_bit);
32}
33
34static dif_result_t get_error_code(const dif_otp_ctrl_t *otp,
35 uint32_t partition_number,
38 field = (bitfield_field32_t){
39 .mask = OTP_CTRL_ERR_CODE_0_ERR_CODE_0_MASK,
40 .index = OTP_CTRL_ERR_CODE_0_ERR_CODE_0_OFFSET,
41 };
42
43 ptrdiff_t err_code_address =
44 OTP_CTRL_ERR_CODE_0_REG_OFFSET +
45 (ptrdiff_t)partition_number * (ptrdiff_t)sizeof(uint32_t);
46 uint32_t error_code = mmio_region_read32(otp->base_addr, err_code_address);
47
48 switch (bitfield_field32_read(error_code, field)) {
49 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_NO_ERROR:
50 *err = kDifOtpCtrlErrorOk;
51 break;
52 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ERROR:
54 break;
55 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ECC_CORR_ERROR:
57 break;
58 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ECC_UNCORR_ERROR:
60 break;
61 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_WRITE_BLANK_ERROR:
63 break;
64 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_ACCESS_ERROR:
66 break;
67 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_CHECK_FAIL_ERROR:
69 break;
70 case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_FSM_STATE_ERROR:
72 break;
73 default:
74 return kDifError;
75 }
76 return kDifOk;
77}
78
79dif_result_t dif_otp_ctrl_configure(const dif_otp_ctrl_t *otp,
80 dif_otp_ctrl_config_t config) {
81 if (otp == NULL) {
82 return kDifBadArg;
83 }
84 if (checks_are_locked(otp, /*check_config=*/true)) {
85 return kDifLocked;
86 }
87
88 mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TIMEOUT_REG_OFFSET,
89 config.check_timeout);
90 mmio_region_write32(otp->base_addr,
91 OTP_CTRL_INTEGRITY_CHECK_PERIOD_REG_OFFSET,
93 mmio_region_write32(otp->base_addr,
94 OTP_CTRL_CONSISTENCY_CHECK_PERIOD_REG_OFFSET,
96
97 return kDifOk;
98}
99
100dif_result_t dif_otp_ctrl_check_integrity(const dif_otp_ctrl_t *otp) {
101 if (otp == NULL) {
102 return kDifBadArg;
103 }
104 if (checks_are_locked(otp, /*check_config=*/false)) {
105 return kDifLocked;
106 }
107
108 uint32_t reg =
109 bitfield_bit32_write(0, OTP_CTRL_CHECK_TRIGGER_INTEGRITY_BIT, true);
110 mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TRIGGER_REG_OFFSET, reg);
111
112 return kDifOk;
113}
114
115dif_result_t dif_otp_ctrl_check_consistency(const dif_otp_ctrl_t *otp) {
116 if (otp == NULL) {
117 return kDifBadArg;
118 }
119 if (checks_are_locked(otp, /*check_config=*/false)) {
120 return kDifLocked;
121 }
122
123 uint32_t reg =
124 bitfield_bit32_write(0, OTP_CTRL_CHECK_TRIGGER_CONSISTENCY_BIT, true);
125 mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TRIGGER_REG_OFFSET, reg);
126
127 return kDifOk;
128}
129
130dif_result_t dif_otp_ctrl_dai_lock(const dif_otp_ctrl_t *otp) {
131 if (otp == NULL) {
132 return kDifBadArg;
133 }
134
135 uint32_t reg = bitfield_bit32_write(
136 0, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false);
137 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET,
138 reg);
139
140 return kDifOk;
141}
142
143dif_result_t dif_otp_ctrl_dai_is_locked(const dif_otp_ctrl_t *otp,
144 bool *is_locked) {
145 if (otp == NULL || is_locked == NULL) {
146 return kDifBadArg;
147 }
148
149 uint32_t reg = mmio_region_read32(otp->base_addr,
150 OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET);
151 *is_locked = !bitfield_bit32_read(
152 reg, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT);
153
154 return kDifOk;
155}
156
157dif_result_t dif_otp_ctrl_lock_config(const dif_otp_ctrl_t *otp) {
158 if (otp == NULL) {
159 return kDifBadArg;
160 }
161
162 uint32_t reg =
163 bitfield_bit32_write(0, OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT, false);
164 mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_REGWEN_REG_OFFSET, reg);
165
166 return kDifOk;
167}
168
169dif_result_t dif_otp_ctrl_config_is_locked(const dif_otp_ctrl_t *otp,
170 bool *is_locked) {
171 if (otp == NULL || is_locked == NULL) {
172 return kDifBadArg;
173 }
174
175 *is_locked = checks_are_locked(otp, /*check_config=*/true);
176 return kDifOk;
177}
178
179dif_result_t dif_otp_ctrl_lock_check_trigger(const dif_otp_ctrl_t *otp) {
180 if (otp == NULL) {
181 return kDifBadArg;
182 }
183
184 uint32_t reg = bitfield_bit32_write(
185 0, OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT, false);
186 mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET,
187 reg);
188
189 return kDifOk;
190}
191
192dif_result_t dif_otp_ctrl_check_trigger_is_locked(const dif_otp_ctrl_t *otp,
193 bool *is_locked) {
194 if (otp == NULL || is_locked == NULL) {
195 return kDifBadArg;
196 }
197
198 *is_locked = checks_are_locked(otp, /*check_config=*/false);
199 return kDifOk;
200}
201
202dif_result_t dif_otp_ctrl_lock_reading(const dif_otp_ctrl_t *otp,
203 otp_partition_t partition) {
204 if (otp == NULL) {
205 return kDifBadArg;
206 }
207
208 dt_otp_partition_info_t partition_info =
209 dt_otp_ctrl_partition(otp->dt, partition);
210 if (!partition_info.read_lockable) {
211 return kDifBadArg;
212 }
213
214 uint32_t reg = bitfield_bit32_write(0, partition_info.read_lock_bit, false);
215 mmio_region_write32(otp->base_addr, (ptrdiff_t)partition_info.lock_reg_offset,
216 reg);
217
218 return kDifOk;
219}
220
221dif_result_t dif_otp_ctrl_reading_is_locked(const dif_otp_ctrl_t *otp,
222 otp_partition_t partition,
223 bool *is_locked) {
224 if (otp == NULL || is_locked == NULL) {
225 return kDifBadArg;
226 }
227
228 dt_otp_partition_info_t partition_info =
229 dt_otp_ctrl_partition(otp->dt, partition);
230 if (!partition_info.read_lockable) {
231 return kDifBadArg;
232 }
233
234 uint32_t reg = mmio_region_read32(otp->base_addr,
235 (ptrdiff_t)partition_info.lock_reg_offset);
236 *is_locked = !bitfield_bit32_read(reg, partition_info.read_lock_bit);
237 return kDifOk;
238}
239
240dif_result_t dif_otp_ctrl_get_status(const dif_otp_ctrl_t *otp,
242 if (otp == NULL || status == NULL) {
243 return kDifBadArg;
244 }
245
246 status->codes = 0;
247
248 // Read main STATUS register
249 uint32_t status_code_reg =
250 mmio_region_read32(otp->base_addr, OTP_CTRL_STATUS_REG_OFFSET);
251
252 // Only read PARTITION_STATUS_0 register if PARTITION_ERROR bit is set
253 if (bitfield_bit32_read(status_code_reg,
254 OTP_CTRL_STATUS_PARTITION_ERROR_BIT)) {
255 uint32_t num_part_status_regs = (kOtpPartitionCount + 31) / 32;
256 for (int status_reg_num = 0; status_reg_num < num_part_status_regs;
257 ++status_reg_num) {
258 uint32_t partition_status_reg = mmio_region_read32(
259 otp->base_addr,
260 (ptrdiff_t)(OTP_CTRL_PARTITION_STATUS_0_REG_OFFSET +
261 (ptrdiff_t)sizeof(uint32_t) * status_reg_num));
262 // Process partition status bits
263 for (int status_idx = 0; status_idx < 32; ++status_idx) {
264 uint32_t partition_number =
265 (uint32_t)status_reg_num * 32 + (uint32_t)status_idx;
266 if (partition_number > kOtpPartitionCount) {
267 break;
268 }
269 // If the error is not present at all, we clear its cause and bail
270 // immediately.
271 if (!bitfield_bit32_read(partition_status_reg,
272 (bitfield_bit32_index_t)status_idx)) {
273 status->causes[partition_number] = kDifOtpCtrlErrorOk;
274 continue;
275 }
276
277 // Set bit for partition error
278 status->codes = bitfield_bit32_write(
279 status->codes,
280 (bitfield_bit32_index_t)OTP_CTRL_STATUS_PARTITION_ERROR_BIT, true);
281
282 // Read and decode err_code register
284 if (get_error_code(otp, partition_number, &err) != kDifOk) {
285 return kDifError;
286 }
287 status->causes[partition_number] = err;
288 }
289 }
290 } else {
291 // No partition errors, clear all partition error causes
292 for (int i = 0; i < kOtpPartitionCount; ++i) {
293 status->causes[i] = kDifOtpCtrlErrorOk;
294 }
295 }
296
297 // Process DAI/LCI status bits from main STATUS register
300 uint32_t err_code_index = kOtpPartitionCount - 1 + (uint32_t)i;
302
303 if (bitfield_bit32_read(status_code_reg, (bitfield_bit32_index_t)i)) {
304 // Set error status code
305 status->codes =
306 bitfield_bit32_write(status->codes, (bitfield_bit32_index_t)i, true);
307
308 // Get error cause
309 if (get_error_code(otp, err_code_index, &err) != kDifOk) {
310 return kDifError;
311 }
312 }
313
314 status->causes[err_code_index] = err;
315 }
316
317 // Process other status bits from main STATUS register
318 for (int i = kDifOtpCtrlStatusCodeLciError + 1; i < ARRAYSIZE(status->causes);
319 ++i) {
320 if (!bitfield_bit32_read(status_code_reg, (bitfield_bit32_index_t)i)) {
321 continue;
322 }
323 // Set error status code
324 status->codes =
325 bitfield_bit32_write(status->codes, (bitfield_bit32_index_t)i, true);
326 }
327
328 return kDifOk;
329}
330
331dif_result_t dif_otp_ctrl_relative_address(const dif_otp_ctrl_t *otp,
332 otp_partition_t partition,
333 uint32_t abs_address,
334 uint32_t *relative_address) {
335 *relative_address = 0;
336
337 if (partition >= kOtpPartitionCount) {
338 return kDifBadArg;
339 }
340
341 dt_otp_partition_info_t partition_info =
342 dt_otp_ctrl_partition(otp->dt, partition);
343 if ((abs_address & partition_info.align_mask) != 0) {
344 return kDifUnaligned;
345 }
346
347 if (abs_address < partition_info.start_addr) {
348 return kDifOutOfRange;
349 }
350
351 *relative_address = abs_address - partition_info.start_addr;
352 // NOTE: `partition_info.size` excludes the digest / zeroization fields.
353 size_t partition_end = partition_info.size;
354 if (partition_info.sw_digest || partition_info.hw_digest) {
355 partition_end += sizeof(uint64_t);
356 }
357 if (partition_info.zeroizable) {
358 partition_end += sizeof(uint64_t);
359 }
360 if (*relative_address >= partition_end) {
361 *relative_address = 0;
362 return kDifOutOfRange;
363 }
364
365 return kDifOk;
366}
367
368dif_result_t dif_otp_ctrl_dai_read_start(const dif_otp_ctrl_t *otp,
369 otp_partition_t partition,
370 uint32_t address) {
371 if (otp == NULL || partition >= kOtpPartitionCount) {
372 return kDifBadArg;
373 }
374
375 dt_otp_partition_info_t partition_info =
376 dt_otp_ctrl_partition(otp->dt, partition);
377 if ((address & partition_info.align_mask) != 0) {
378 return kDifUnaligned;
379 }
380
381 // NOTE: `partition_info.size` excludes the digest / zeroization fields.
382 size_t partition_end = partition_info.size;
383 if (partition_info.sw_digest || partition_info.hw_digest) {
384 partition_end += sizeof(uint64_t);
385 }
386 if (partition_info.zeroizable) {
387 partition_end += sizeof(uint64_t);
388 }
389 if (address >= partition_end) {
390 return kDifOutOfRange;
391 }
392
393 address += partition_info.start_addr;
394 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET,
395 address);
396
397 uint32_t cmd =
398 bitfield_bit32_write(0, OTP_CTRL_DIRECT_ACCESS_CMD_RD_BIT, true);
399 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET,
400 cmd);
401
402 return kDifOk;
403}
404
405dif_result_t dif_otp_ctrl_dai_read32_end(const dif_otp_ctrl_t *otp,
406 uint32_t *value) {
407 if (otp == NULL || value == NULL) {
408 return kDifBadArg;
409 }
410
411 *value = mmio_region_read32(otp->base_addr,
412 OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET);
413 return kDifOk;
414}
415
416dif_result_t dif_otp_ctrl_dai_read64_end(const dif_otp_ctrl_t *otp,
417 uint64_t *value) {
418 if (otp == NULL || value == NULL) {
419 return kDifBadArg;
420 }
421
422 *value = mmio_region_read32(otp->base_addr,
423 OTP_CTRL_DIRECT_ACCESS_RDATA_1_REG_OFFSET);
424 *value <<= 32;
425 *value |= mmio_region_read32(otp->base_addr,
426 OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET);
427 return kDifOk;
428}
429
430dif_result_t dif_otp_ctrl_dai_program32(const dif_otp_ctrl_t *otp,
431 otp_partition_t partition,
432 uint32_t address, uint32_t value) {
433 if (otp == NULL || partition >= kOtpPartitionCount) {
434 return kDifBadArg;
435 }
436 dt_otp_partition_info_t partition_info =
437 dt_otp_ctrl_partition(otp->dt, partition);
438
439 // Ensure that we are writing to a 32-bit-access partition by checking that
440 // the alignment mask is 0b11.
441 //
442 // Note furthermore that the LC partition is *not* writeable, so we eject
443 // here.
444 if (partition_info.align_mask != 0x3 || partition_info.is_lifecycle) {
445 return kDifError;
446 }
447
448 if ((address & partition_info.align_mask) != 0) {
449 return kDifUnaligned;
450 }
451
452 // NOTE: `partition_info.size` excludes the digest / zeroization fields.
453 size_t partition_end = partition_info.size;
454 if (partition_info.sw_digest || partition_info.hw_digest) {
455 partition_end += sizeof(uint64_t);
456 }
457 if (partition_info.zeroizable) {
458 partition_end += sizeof(uint64_t);
459 }
460 if (address >= partition_end) {
461 return kDifOutOfRange;
462 }
463
464 address += partition_info.start_addr;
465 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET,
466 address);
467
468 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET,
469 value);
470
471 uint32_t cmd =
472 bitfield_bit32_write(0, OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true);
473 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET,
474 cmd);
475
476 return kDifOk;
477}
478
479dif_result_t dif_otp_ctrl_dai_program64(const dif_otp_ctrl_t *otp,
480 otp_partition_t partition,
481 uint32_t address, uint64_t value) {
482 if (otp == NULL || partition >= kOtpPartitionCount) {
483 return kDifBadArg;
484 }
485 dt_otp_partition_info_t partition_info =
486 dt_otp_ctrl_partition(otp->dt, partition);
487
488 // Some partitions are not accessible by software.
489 if (partition_info.is_lifecycle) {
490 return kDifError;
491 }
492 // Ensure that we are writing to a 64-bit-access partition by checking that
493 // the alignment mask is 0b111.
494 if (partition_info.align_mask != 0x7) {
495 return kDifError;
496 }
497
498 if ((address & partition_info.align_mask) != 0) {
499 return kDifUnaligned;
500 }
501
502 // NOTE: `partition_info.size` excludes the digest / zeroization fields.
503 size_t partition_end = partition_info.size;
504 if (partition_info.sw_digest || partition_info.hw_digest) {
505 partition_end += sizeof(uint64_t);
506 }
507 if (partition_info.zeroizable) {
508 partition_end += sizeof(uint64_t);
509 }
510 if (address >= partition_end) {
511 return kDifOutOfRange;
512 }
513
514 address += partition_info.start_addr;
515 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET,
516 address);
517
518 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET,
519 value & UINT32_MAX);
520 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET,
521 value >> 32);
522
523 uint32_t cmd =
524 bitfield_bit32_write(0, OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true);
525 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET,
526 cmd);
527
528 return kDifOk;
529}
530
531dif_result_t dif_otp_ctrl_dai_digest(const dif_otp_ctrl_t *otp,
532 otp_partition_t partition,
533 uint64_t digest) {
534 if (otp == NULL || partition >= kOtpPartitionCount) {
535 return kDifBadArg;
536 }
537 dt_otp_partition_info_t partition_info =
538 dt_otp_ctrl_partition(otp->dt, partition);
539
540 // Not all partitions have a digest.
541 if (!partition_info.sw_digest && !partition_info.hw_digest) {
542 return kDifBadArg;
543 }
544
545 // For software partitions, the digest must be nonzero; for all other
546 // partitions it must be zero.
547 bool is_sw = partition_info.sw_digest;
548 if (is_sw == (digest == 0)) {
549 return kDifBadArg;
550 }
551
552 uint32_t address = partition_info.start_addr;
553 if (is_sw) {
554 // NOTE: `partition_info.size` excludes the digest / zeroization fields.
555 address += partition_info.size;
556 }
557 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET,
558 address);
559
560 if (digest != 0) {
561 mmio_region_write32(otp->base_addr,
562 OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET,
563 digest & 0xffffffff);
564 mmio_region_write32(otp->base_addr,
565 OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET,
566 digest >> 32);
567 }
568
569 bitfield_bit32_index_t cmd_bit = is_sw
570 ? OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT
571 : OTP_CTRL_DIRECT_ACCESS_CMD_DIGEST_BIT;
572 uint32_t cmd = bitfield_bit32_write(0, cmd_bit, true);
573 mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET,
574 cmd);
575
576 return kDifOk;
577}
578
579dif_result_t dif_otp_ctrl_is_digest_computed(const dif_otp_ctrl_t *otp,
580 otp_partition_t partition,
581 bool *is_computed) {
582 if (otp == NULL || is_computed == NULL) {
583 return kDifBadArg;
584 }
585 dt_otp_partition_info_t partition_info =
586 dt_otp_ctrl_partition(otp->dt, partition);
587
588 // Not all partitions have a digest.
589 if (!partition_info.sw_digest && !partition_info.hw_digest) {
590 return kDifBadArg;
591 }
592
593 uint64_t value = mmio_region_read32(
594 otp->base_addr,
595 (ptrdiff_t)(partition_info.digest_reg_offset + sizeof(uint32_t)));
596 value <<= 32;
597 value |= mmio_region_read32(otp->base_addr,
598 (ptrdiff_t)partition_info.digest_reg_offset);
599
600 *is_computed = value != 0;
601
602 return kDifOk;
603}
604
605dif_result_t dif_otp_ctrl_get_digest(const dif_otp_ctrl_t *otp,
606 otp_partition_t partition,
607 uint64_t *digest) {
608 if (otp == NULL || digest == NULL) {
609 return kDifBadArg;
610 }
611 dt_otp_partition_info_t partition_info =
612 dt_otp_ctrl_partition(otp->dt, partition);
613
614 // Not all partitions have a digest.
615 if (!partition_info.sw_digest && !partition_info.hw_digest) {
616 return kDifBadArg;
617 }
618
619 uint64_t value = mmio_region_read32(
620 otp->base_addr,
621 (ptrdiff_t)(partition_info.digest_reg_offset + sizeof(uint32_t)));
622 value <<= 32;
623 value |= mmio_region_read32(otp->base_addr,
624 (ptrdiff_t)partition_info.digest_reg_offset);
625
626 if (value == 0) {
627 return kDifError;
628 }
629 *digest = value;
630
631 return kDifOk;
632}
633
634dif_result_t dif_otp_ctrl_read_blocking(const dif_otp_ctrl_t *otp,
635 otp_partition_t partition,
636 uint32_t address, uint32_t *buf,
637 size_t len) {
638 if (otp == NULL || partition >= kOtpPartitionCount || buf == NULL) {
639 return kDifBadArg;
640 }
641 dt_otp_partition_info_t partition_info =
642 dt_otp_ctrl_partition(otp->dt, partition);
643
644 if (!partition_info.sw_digest) {
645 return kDifError;
646 }
647
648 if ((address & partition_info.align_mask) != 0) {
649 return kDifUnaligned;
650 }
651
652 if (address + len >= partition_info.size) {
653 return kDifOutOfRange;
654 }
655
656 uint32_t reg_offset =
657 OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + partition_info.start_addr + address;
658 mmio_region_memcpy_from_mmio32(otp->base_addr, reg_offset, buf,
659 len * sizeof(uint32_t));
660 return kDifOk;
661}