1 /**
2 * \file
3 *
4 * \brief SAM Non Volatile Memory driver
5 *
6 * Copyright (C) 2012-2016 Atmel Corporation. All rights reserved.
7 *
8 * \asf_license_start
9 *
10 * \page License
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright notice,
16 * this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above copyright notice,
19 * this list of conditions and the following disclaimer in the documentation
20 * and/or other materials provided with the distribution.
21 *
22 * 3. The name of Atmel may not be used to endorse or promote products derived
23 * from this software without specific prior written permission.
24 *
25 * 4. This software may only be redistributed and used in connection with an
26 * Atmel microcontroller product.
27 *
28 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
29 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
30 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
31 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
32 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
37 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38 * POSSIBILITY OF SUCH DAMAGE.
39 *
40 * \asf_license_stop
41 *
42 */
43 /*
44 * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
45 */
46 #include "nvm.h"
47 #include <system.h>
48 #include <system_interrupt.h>
49 #include <string.h>
50
51 /**
52 * \internal Internal device instance struct
53 *
54 * This struct contains information about the NVM module which is
55 * often used by the different functions. The information is loaded
56 * into the struct in the nvm_init() function.
57 */
58 struct _nvm_module {
59 /** Number of bytes contained per page. */
60 uint16_t page_size;
61 /** Total number of pages in the NVM memory. */
62 uint16_t number_of_pages;
63 /** If \c false, a page write command will be issued automatically when the
64 * page buffer is full. */
65 bool manual_page_write;
66 };
67
68 /**
69 * \internal Instance of the internal device struct
70 */
71 static struct _nvm_module _nvm_dev;
72
73 /**
74 * \internal Pointer to the NVM MEMORY region start address
75 */
76 #define NVM_MEMORY ((volatile uint16_t *)FLASH_ADDR)
77
78 /**
79 * \internal Pointer to the NVM USER MEMORY region start address
80 */
81 #define NVM_USER_MEMORY ((volatile uint16_t *)NVMCTRL_USER)
82
83
84 /**
85 * \brief Sets the up the NVM hardware module based on the configuration.
86 *
87 * Writes a given configuration of an NVM controller configuration to the
88 * hardware module, and initializes the internal device struct.
89 *
90 * \param[in] config Configuration settings for the NVM controller
91 *
92 * \note The security bit must be cleared in order successfully use this
93 * function. This can only be done by a chip erase.
94 *
95 * \return Status of the configuration procedure.
96 *
97 * \retval STATUS_OK If the initialization was a success
98 * \retval STATUS_BUSY If the module was busy when the operation was attempted
99 * \retval STATUS_ERR_IO If the security bit has been set, preventing the
100 * EEPROM and/or auxiliary space configuration from being
101 * altered
102 */
nvm_set_config(const struct nvm_config * const config)103 enum status_code nvm_set_config(
104 const struct nvm_config *const config)
105 {
106 /* Sanity check argument */
107 Assert(config);
108
109 /* Get a pointer to the module hardware instance */
110 Nvmctrl *const nvm_module = NVMCTRL;
111
112 #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30)
113 /* Turn on the digital interface clock */
114 system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBB, MCLK_APBBMASK_NVMCTRL);
115 #else
116 /* Turn on the digital interface clock */
117 system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBB, PM_APBBMASK_NVMCTRL);
118 #endif
119
120 /* Clear error flags */
121 nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK;
122
123 /* Check if the module is busy */
124 if (!nvm_is_ready()) {
125 return STATUS_BUSY;
126 }
127
128 #if (!SAMC20) && (!SAMC21)
129 /* Writing configuration to the CTRLB register */
130 nvm_module->CTRLB.reg =
131 NVMCTRL_CTRLB_SLEEPPRM(config->sleep_power_mode) |
132 ((config->manual_page_write & 0x01) << NVMCTRL_CTRLB_MANW_Pos) |
133 NVMCTRL_CTRLB_RWS(config->wait_states) |
134 ((config->disable_cache & 0x01) << NVMCTRL_CTRLB_CACHEDIS_Pos) |
135 NVMCTRL_CTRLB_READMODE(config->cache_readmode);
136 #else
137 uint8_t cache_disable_value = 0;
138 if (config->disable_rww_cache == false) {
139 cache_disable_value = 0x02;
140 } else {
141 cache_disable_value = (config->disable_cache & 0x01);
142 }
143 /* Writing configuration to the CTRLB register */
144 nvm_module->CTRLB.reg =
145 NVMCTRL_CTRLB_SLEEPPRM(config->sleep_power_mode) |
146 ((config->manual_page_write & 0x01) << NVMCTRL_CTRLB_MANW_Pos) |
147 NVMCTRL_CTRLB_RWS(config->wait_states) |
148 (cache_disable_value << NVMCTRL_CTRLB_CACHEDIS_Pos) |
149 NVMCTRL_CTRLB_READMODE(config->cache_readmode);
150 #endif
151
152 /* Initialize the internal device struct */
153 _nvm_dev.page_size = (8 << nvm_module->PARAM.bit.PSZ);
154 _nvm_dev.number_of_pages = nvm_module->PARAM.bit.NVMP;
155 _nvm_dev.manual_page_write = config->manual_page_write;
156
157 /* If the security bit is set, the auxiliary space cannot be written */
158 if (nvm_module->STATUS.reg & NVMCTRL_STATUS_SB) {
159 return STATUS_ERR_IO;
160 }
161
162 return STATUS_OK;
163 }
164
165 /**
166 * \brief Executes a command on the NVM controller.
167 *
168 * Executes an asynchronous command on the NVM controller, to perform a requested
169 * action such as an NVM page read or write operation.
170 *
171 * \note The function will return before the execution of the given command is
172 * completed.
173 *
174 * \param[in] command Command to issue to the NVM controller
175 * \param[in] address Address to pass to the NVM controller in NVM memory
176 * space
177 * \param[in] parameter Parameter to pass to the NVM controller, not used
178 * for this driver
179 *
180 * \return Status of the attempt to execute a command.
181 *
182 * \retval STATUS_OK If the command was accepted and execution
183 * is now in progress
184 * \retval STATUS_BUSY If the NVM controller was already busy
185 * executing a command when the new command
186 * was issued
187 * \retval STATUS_ERR_IO If the command was invalid due to memory or
188 * security locking
189 * \retval STATUS_ERR_INVALID_ARG If the given command was invalid or
190 * unsupported
191 * \retval STATUS_ERR_BAD_ADDRESS If the given address was invalid
192 */
nvm_execute_command(const enum nvm_command command,const uint32_t address,const uint32_t parameter)193 enum status_code nvm_execute_command(
194 const enum nvm_command command,
195 const uint32_t address,
196 const uint32_t parameter)
197 {
198 uint32_t ctrlb_bak;
199
200 /* Check that the address given is valid */
201 if (address > ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages)
202 && !(address >= NVMCTRL_AUX0_ADDRESS && address <= NVMCTRL_AUX1_ADDRESS )){
203 #ifdef FEATURE_NVM_RWWEE
204 if (address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR)
205 || address < NVMCTRL_RWW_EEPROM_ADDR){
206 return STATUS_ERR_BAD_ADDRESS;
207 }
208 #else
209 return STATUS_ERR_BAD_ADDRESS;
210 #endif
211 }
212
213 /* Get a pointer to the module hardware instance */
214 Nvmctrl *const nvm_module = NVMCTRL;
215
216 /* Turn off cache before issuing flash commands */
217 ctrlb_bak = nvm_module->CTRLB.reg;
218 #if (SAMC20) || (SAMC21)
219 nvm_module->CTRLB.reg = ((ctrlb_bak &(~(NVMCTRL_CTRLB_CACHEDIS(0x2))))
220 | NVMCTRL_CTRLB_CACHEDIS(0x1));
221 #else
222 nvm_module->CTRLB.reg = ctrlb_bak | NVMCTRL_CTRLB_CACHEDIS;
223 #endif
224
225 /* Clear error flags */
226 nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK;
227
228 /* Check if the module is busy */
229 if (!nvm_is_ready()) {
230 /* Restore the setting */
231 nvm_module->CTRLB.reg = ctrlb_bak;
232 return STATUS_BUSY;
233 }
234
235 switch (command) {
236
237 /* Commands requiring address (protected) */
238 case NVM_COMMAND_ERASE_AUX_ROW:
239 case NVM_COMMAND_WRITE_AUX_ROW:
240
241 /* Auxiliary space cannot be accessed if the security bit is set */
242 if (nvm_module->STATUS.reg & NVMCTRL_STATUS_SB) {
243 /* Restore the setting */
244 nvm_module->CTRLB.reg = ctrlb_bak;
245 return STATUS_ERR_IO;
246 }
247
248 /* Set address, command will be issued elsewhere */
249 nvm_module->ADDR.reg = (uintptr_t)&NVM_MEMORY[address / 4];
250 break;
251
252 /* Commands requiring address (unprotected) */
253 case NVM_COMMAND_ERASE_ROW:
254 case NVM_COMMAND_WRITE_PAGE:
255 case NVM_COMMAND_LOCK_REGION:
256 case NVM_COMMAND_UNLOCK_REGION:
257 #ifdef FEATURE_NVM_RWWEE
258 case NVM_COMMAND_RWWEE_ERASE_ROW:
259 case NVM_COMMAND_RWWEE_WRITE_PAGE:
260 #endif
261
262 /* Set address, command will be issued elsewhere */
263 nvm_module->ADDR.reg = (uintptr_t)&NVM_MEMORY[address / 4];
264 break;
265
266 /* Commands not requiring address */
267 case NVM_COMMAND_PAGE_BUFFER_CLEAR:
268 case NVM_COMMAND_SET_SECURITY_BIT:
269 case NVM_COMMAND_ENTER_LOW_POWER_MODE:
270 case NVM_COMMAND_EXIT_LOW_POWER_MODE:
271 break;
272
273 default:
274 /* Restore the setting */
275 nvm_module->CTRLB.reg = ctrlb_bak;
276 return STATUS_ERR_INVALID_ARG;
277 }
278
279 /* Set command */
280 nvm_module->CTRLA.reg = command | NVMCTRL_CTRLA_CMDEX_KEY;
281
282 /* Wait for the NVM controller to become ready */
283 while (!nvm_is_ready()) {
284 }
285
286 /* Restore the setting */
287 nvm_module->CTRLB.reg = ctrlb_bak;
288
289 return STATUS_OK;
290 }
291
292 /**
293 * \brief Updates an arbitrary section of a page with new data.
294 *
295 * Writes from a buffer to a given page in the NVM memory, retaining any
296 * unmodified data already stored in the page.
297 *
298 * \note If manual write mode is enable, the write command must be executed after
299 * this function, otherwise the data will not write to NVM from page buffer.
300 *
301 * \warning This routine is unsafe if data integrity is critical; a system reset
302 * during the update process will result in up to one row of data being
303 * lost. If corruption must be avoided in all circumstances (including
304 * power loss or system reset) this function should not be used.
305 *
306 * \param[in] destination_address Destination page address to write to
307 * \param[in] buffer Pointer to buffer where the data to write is
308 * stored
309 * \param[in] offset Number of bytes to offset the data write in
310 * the page
311 * \param[in] length Number of bytes in the page to update
312 *
313 * \return Status of the attempt to update a page.
314 *
315 * \retval STATUS_OK Requested NVM memory page was successfully
316 * read
317 * \retval STATUS_BUSY NVM controller was busy when the operation
318 * was attempted
319 * \retval STATUS_ERR_BAD_ADDRESS The requested address was outside the
320 * acceptable range of the NVM memory region
321 * \retval STATUS_ERR_INVALID_ARG The supplied length and offset was invalid
322 */
nvm_update_buffer(const uint32_t destination_address,uint8_t * const buffer,uint16_t offset,uint16_t length)323 enum status_code nvm_update_buffer(
324 const uint32_t destination_address,
325 uint8_t *const buffer,
326 uint16_t offset,
327 uint16_t length)
328 {
329 enum status_code error_code = STATUS_OK;
330 uint8_t row_buffer[NVMCTRL_ROW_PAGES][NVMCTRL_PAGE_SIZE];
331
332 /* Ensure the read does not overflow the page size */
333 if ((offset + length) > _nvm_dev.page_size) {
334 return STATUS_ERR_INVALID_ARG;
335 }
336
337 /* Calculate the starting row address of the page to update */
338 uint32_t row_start_address =
339 destination_address & ~((_nvm_dev.page_size * NVMCTRL_ROW_PAGES) - 1);
340
341 /* Read in the current row contents */
342 for (uint32_t i = 0; i < NVMCTRL_ROW_PAGES; i++) {
343 do
344 {
345 error_code = nvm_read_buffer(
346 row_start_address + (i * _nvm_dev.page_size),
347 row_buffer[i], _nvm_dev.page_size);
348 } while (error_code == STATUS_BUSY);
349
350 if (error_code != STATUS_OK) {
351 return error_code;
352 }
353 }
354
355 /* Calculate the starting page in the row that is to be updated */
356 uint8_t page_in_row =
357 (destination_address % (_nvm_dev.page_size * NVMCTRL_ROW_PAGES)) /
358 _nvm_dev.page_size;
359
360 /* Update the specified bytes in the page buffer */
361 for (uint32_t i = 0; i < length; i++) {
362 row_buffer[page_in_row][offset + i] = buffer[i];
363 }
364
365 system_interrupt_enter_critical_section();
366
367 /* Erase the row */
368 do
369 {
370 error_code = nvm_erase_row(row_start_address);
371 } while (error_code == STATUS_BUSY);
372
373 if (error_code != STATUS_OK) {
374 system_interrupt_leave_critical_section();
375 return error_code;
376 }
377
378 /* Write the updated row contents to the erased row */
379 for (uint32_t i = 0; i < NVMCTRL_ROW_PAGES; i++) {
380 do
381 {
382 error_code = nvm_write_buffer(
383 row_start_address + (i * _nvm_dev.page_size),
384 row_buffer[i], _nvm_dev.page_size);
385 } while (error_code == STATUS_BUSY);
386
387 if (error_code != STATUS_OK) {
388 system_interrupt_leave_critical_section();
389 return error_code;
390 }
391 }
392
393 system_interrupt_leave_critical_section();
394
395 return error_code;
396 }
397
398 /**
399 * \brief Writes a number of bytes to a page in the NVM memory region.
400 *
401 * Writes from a buffer to a given page address in the NVM memory.
402 *
403 * \param[in] destination_address Destination page address to write to
404 * \param[in] buffer Pointer to buffer where the data to write is
405 * stored
406 * \param[in] length Number of bytes in the page to write
407 *
408 * \note If writing to a page that has previously been written to, the page's
409 * row should be erased (via \ref nvm_erase_row()) before attempting to
410 * write new data to the page.
411 *
412 * \note For SAM D21 RWW devices, see \c SAMD21_64K, command \c NVM_COMMAND_RWWEE_WRITE_PAGE
413 * must be executed before any other commands after writing a page,
414 * refer to errata 13588.
415 *
416 * \note If manual write mode is enabled, the write command must be executed after
417 * this function, otherwise the data will not write to NVM from page buffer.
418 *
419 * \return Status of the attempt to write a page.
420 *
421 * \retval STATUS_OK Requested NVM memory page was successfully
422 * read
423 * \retval STATUS_BUSY NVM controller was busy when the operation
424 * was attempted
425 * \retval STATUS_ERR_BAD_ADDRESS The requested address was outside the
426 * acceptable range of the NVM memory region or
427 * not aligned to the start of a page
428 * \retval STATUS_ERR_INVALID_ARG The supplied write length was invalid
429 */
nvm_write_buffer(const uint32_t destination_address,const uint8_t * buffer,uint16_t length)430 enum status_code nvm_write_buffer(
431 const uint32_t destination_address,
432 const uint8_t *buffer,
433 uint16_t length)
434 {
435 #ifdef FEATURE_NVM_RWWEE
436 bool is_rww_eeprom = false;
437 #endif
438
439 /* Check if the destination address is valid */
440 if (destination_address >
441 ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages)) {
442 #ifdef FEATURE_NVM_RWWEE
443 if (destination_address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR)
444 || destination_address < NVMCTRL_RWW_EEPROM_ADDR){
445 return STATUS_ERR_BAD_ADDRESS;
446 }
447 is_rww_eeprom = true;
448 #else
449 return STATUS_ERR_BAD_ADDRESS;
450 #endif
451 }
452
453 /* Check if the write address not aligned to the start of a page */
454 if (destination_address & (_nvm_dev.page_size - 1)) {
455 return STATUS_ERR_BAD_ADDRESS;
456 }
457
458 /* Check if the write length is longer than an NVM page */
459 if (length > _nvm_dev.page_size) {
460 return STATUS_ERR_INVALID_ARG;
461 }
462
463 /* Get a pointer to the module hardware instance */
464 Nvmctrl *const nvm_module = NVMCTRL;
465
466 /* Check if the module is busy */
467 if (!nvm_is_ready()) {
468 return STATUS_BUSY;
469 }
470
471 /* Erase the page buffer before buffering new data */
472 nvm_module->CTRLA.reg = NVM_COMMAND_PAGE_BUFFER_CLEAR | NVMCTRL_CTRLA_CMDEX_KEY;
473
474 /* Check if the module is busy */
475 while (!nvm_is_ready()) {
476 /* Force-wait for the buffer clear to complete */
477 }
478
479 /* Clear error flags */
480 nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK;
481
482 uint32_t nvm_address = destination_address / 2;
483
484 /* NVM _must_ be accessed as a series of 16-bit words, perform manual copy
485 * to ensure alignment */
486 for (uint16_t i = 0; i < length; i += 2) {
487 uint16_t data;
488
489 /* Copy first byte of the 16-bit chunk to the temporary buffer */
490 data = buffer[i];
491
492 /* If we are not at the end of a write request with an odd byte count,
493 * store the next byte of data as well */
494 if (i < (length - 1)) {
495 data |= (buffer[i + 1] << 8);
496 }
497
498 /* Store next 16-bit chunk to the NVM memory space */
499 NVM_MEMORY[nvm_address++] = data;
500 }
501
502 /* If automatic page write mode is enable, then perform a manual NVM
503 * write when the length of data to be programmed is less than page size
504 */
505 if ((_nvm_dev.manual_page_write == false) && (length < NVMCTRL_PAGE_SIZE)) {
506 #ifdef FEATURE_NVM_RWWEE
507 return ((is_rww_eeprom) ?
508 (nvm_execute_command(NVM_COMMAND_RWWEE_WRITE_PAGE,destination_address, 0)):
509 (nvm_execute_command(NVM_COMMAND_WRITE_PAGE,destination_address, 0)));
510 #else
511 return nvm_execute_command(NVM_COMMAND_WRITE_PAGE,
512 destination_address, 0);
513 #endif
514 }
515
516 return STATUS_OK;
517 }
518
519 /**
520 * \brief Reads a number of bytes from a page in the NVM memory region.
521 *
522 * Reads a given number of bytes from a given page address in the NVM memory
523 * space into a buffer.
524 *
525 * \param[in] source_address Source page address to read from
526 * \param[out] buffer Pointer to a buffer where the content of the read
527 * page will be stored
528 * \param[in] length Number of bytes in the page to read
529 *
530 * \return Status of the page read attempt.
531 *
532 * \retval STATUS_OK Requested NVM memory page was successfully
533 * read
534 * \retval STATUS_BUSY NVM controller was busy when the operation
535 * was attempted
536 * \retval STATUS_ERR_BAD_ADDRESS The requested address was outside the
537 * acceptable range of the NVM memory region or
538 * not aligned to the start of a page
539 * \retval STATUS_ERR_INVALID_ARG The supplied read length was invalid
540 */
nvm_read_buffer(const uint32_t source_address,uint8_t * const buffer,uint16_t length)541 enum status_code nvm_read_buffer(
542 const uint32_t source_address,
543 uint8_t *const buffer,
544 uint16_t length)
545 {
546 /* Check if the source address is valid */
547 if (source_address >
548 ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages)) {
549 #ifdef FEATURE_NVM_RWWEE
550 if (source_address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR)
551 || source_address < NVMCTRL_RWW_EEPROM_ADDR){
552 return STATUS_ERR_BAD_ADDRESS;
553 }
554 #else
555 return STATUS_ERR_BAD_ADDRESS;
556 #endif
557 }
558
559 /* Check if the read address is not aligned to the start of a page */
560 if (source_address & (_nvm_dev.page_size - 1)) {
561 return STATUS_ERR_BAD_ADDRESS;
562 }
563
564 /* Check if the write length is longer than an NVM page */
565 if (length > _nvm_dev.page_size) {
566 return STATUS_ERR_INVALID_ARG;
567 }
568
569 /* Get a pointer to the module hardware instance */
570 Nvmctrl *const nvm_module = NVMCTRL;
571
572 /* Check if the module is busy */
573 if (!nvm_is_ready()) {
574 return STATUS_BUSY;
575 }
576
577 /* Clear error flags */
578 nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK;
579
580 uint32_t page_address = source_address / 2;
581
582 /* NVM _must_ be accessed as a series of 16-bit words, perform manual copy
583 * to ensure alignment */
584 for (uint16_t i = 0; i < length; i += 2) {
585 /* Fetch next 16-bit chunk from the NVM memory space */
586 uint16_t data = NVM_MEMORY[page_address++];
587
588 /* Copy first byte of the 16-bit chunk to the destination buffer */
589 buffer[i] = (data & 0xFF);
590
591 /* If we are not at the end of a read request with an odd byte count,
592 * store the next byte of data as well */
593 if (i < (length - 1)) {
594 buffer[i + 1] = (data >> 8);
595 }
596 }
597
598 return STATUS_OK;
599 }
600
601 /**
602 * \brief Erases a row in the NVM memory space.
603 *
604 * Erases a given row in the NVM memory region.
605 *
606 * \param[in] row_address Address of the row to erase
607 *
608 * \return Status of the NVM row erase attempt.
609 *
610 * \retval STATUS_OK Requested NVM memory row was successfully
611 * erased
612 * \retval STATUS_BUSY NVM controller was busy when the operation
613 * was attempted
614 * \retval STATUS_ERR_BAD_ADDRESS The requested row address was outside the
615 * acceptable range of the NVM memory region or
616 * not aligned to the start of a row
617 * \retval STATUS_ABORTED NVM erased error
618 */
nvm_erase_row(const uint32_t row_address)619 enum status_code nvm_erase_row(
620 const uint32_t row_address)
621 {
622 #ifdef FEATURE_NVM_RWWEE
623 bool is_rww_eeprom = false;
624 #endif
625
626 /* Check if the row address is valid */
627 if (row_address >
628 ((uint32_t)_nvm_dev.page_size * _nvm_dev.number_of_pages)) {
629 #ifdef FEATURE_NVM_RWWEE
630 if (row_address >= ((uint32_t)NVMCTRL_RWW_EEPROM_SIZE + NVMCTRL_RWW_EEPROM_ADDR)
631 || row_address < NVMCTRL_RWW_EEPROM_ADDR){
632 return STATUS_ERR_BAD_ADDRESS;
633 }
634 is_rww_eeprom = true;
635 #else
636 return STATUS_ERR_BAD_ADDRESS;
637 #endif
638 }
639
640 /* Check if the address to erase is not aligned to the start of a row */
641 if (row_address & ((_nvm_dev.page_size * NVMCTRL_ROW_PAGES) - 1)) {
642 return STATUS_ERR_BAD_ADDRESS;
643 }
644
645 /* Get a pointer to the module hardware instance */
646 Nvmctrl *const nvm_module = NVMCTRL;
647
648 /* Check if the module is busy */
649 if (!nvm_is_ready()) {
650 return STATUS_BUSY;
651 }
652
653 /* Clear error flags */
654 nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK;
655
656 /* Set address and command */
657 nvm_module->ADDR.reg = (uintptr_t)&NVM_MEMORY[row_address / 4];
658
659 #ifdef SAMD21_64K
660 if (is_rww_eeprom) {
661 NVM_MEMORY[row_address / 2] = 0x0;
662 }
663 #endif
664
665 #ifdef FEATURE_NVM_RWWEE
666 nvm_module->CTRLA.reg = ((is_rww_eeprom) ?
667 (NVM_COMMAND_RWWEE_ERASE_ROW | NVMCTRL_CTRLA_CMDEX_KEY):
668 (NVM_COMMAND_ERASE_ROW | NVMCTRL_CTRLA_CMDEX_KEY));
669 #else
670 nvm_module->CTRLA.reg = NVM_COMMAND_ERASE_ROW | NVMCTRL_CTRLA_CMDEX_KEY;
671 #endif
672
673 while (!nvm_is_ready()) {
674 }
675
676 /* There existed error in NVM erase operation */
677 if ((enum nvm_error)(nvm_module->STATUS.reg & NVM_ERRORS_MASK) != NVM_ERROR_NONE) {
678 return STATUS_ABORTED;
679 }
680
681 return STATUS_OK;
682 }
683
684 /**
685 * \brief Reads the parameters of the NVM controller.
686 *
687 * Retrieves the page size, number of pages, and other configuration settings
688 * of the NVM region.
689 *
690 * \param[out] parameters Parameter structure, which holds page size and
691 * number of pages in the NVM memory
692 */
nvm_get_parameters(struct nvm_parameters * const parameters)693 void nvm_get_parameters(
694 struct nvm_parameters *const parameters)
695 {
696 /* Sanity check parameters */
697 Assert(parameters);
698
699 /* Get a pointer to the module hardware instance */
700 Nvmctrl *const nvm_module = NVMCTRL;
701
702 /* Clear error flags */
703 nvm_module->STATUS.reg = NVMCTRL_STATUS_MASK;
704
705 /* Read out from the PARAM register */
706 uint32_t param_reg = nvm_module->PARAM.reg;
707
708 /* Mask out page size exponent and convert to a number of bytes */
709 parameters->page_size =
710 8 << ((param_reg & NVMCTRL_PARAM_PSZ_Msk) >> NVMCTRL_PARAM_PSZ_Pos);
711
712 /* Mask out number of pages count */
713 parameters->nvm_number_of_pages =
714 (param_reg & NVMCTRL_PARAM_NVMP_Msk) >> NVMCTRL_PARAM_NVMP_Pos;
715
716 #ifdef FEATURE_NVM_RWWEE
717 /* Mask out rwwee number of pages count */
718 parameters->rww_eeprom_number_of_pages =
719 (param_reg & NVMCTRL_PARAM_RWWEEP_Msk) >> NVMCTRL_PARAM_RWWEEP_Pos;
720 #endif
721
722 /* Read the current EEPROM fuse value from the USER row */
723 uint16_t eeprom_fuse_value =
724 (NVM_USER_MEMORY[NVMCTRL_FUSES_EEPROM_SIZE_Pos / 16] &
725 NVMCTRL_FUSES_EEPROM_SIZE_Msk) >> NVMCTRL_FUSES_EEPROM_SIZE_Pos;
726
727 /* Translate the EEPROM fuse byte value to a number of NVM pages */
728 if (eeprom_fuse_value == 7) {
729 parameters->eeprom_number_of_pages = 0;
730 }
731 else {
732 parameters->eeprom_number_of_pages =
733 NVMCTRL_ROW_PAGES << (6 - eeprom_fuse_value);
734 }
735
736 /* Read the current BOOTSZ fuse value from the USER row */
737 uint16_t boot_fuse_value =
738 (NVM_USER_MEMORY[NVMCTRL_FUSES_BOOTPROT_Pos / 16] &
739 NVMCTRL_FUSES_BOOTPROT_Msk) >> NVMCTRL_FUSES_BOOTPROT_Pos;
740
741 /* Translate the BOOTSZ fuse byte value to a number of NVM pages */
742 if (boot_fuse_value == 7) {
743 parameters->bootloader_number_of_pages = 0;
744 }
745 else {
746 parameters->bootloader_number_of_pages =
747 NVMCTRL_ROW_PAGES << (7 - boot_fuse_value);
748 }
749 }
750
751 /**
752 * \brief Checks whether the page region is locked.
753 *
754 * Extracts the region to which the given page belongs and checks whether
755 * that region is locked.
756 *
757 * \param[in] page_number Page number to be checked
758 *
759 * \return Page lock status.
760 *
761 * \retval true Page is locked
762 * \retval false Page is not locked
763 *
764 */
nvm_is_page_locked(uint16_t page_number)765 bool nvm_is_page_locked(uint16_t page_number)
766 {
767 uint16_t pages_in_region;
768 uint16_t region_number;
769
770 #ifdef FEATURE_NVM_RWWEE
771 Assert(page_number < _nvm_dev.number_of_pages);
772 #endif
773
774 /* Get a pointer to the module hardware instance */
775 Nvmctrl *const nvm_module = NVMCTRL;
776
777 /* Get number of pages in a region */
778 pages_in_region = _nvm_dev.number_of_pages / 16;
779
780 /* Get region for given page */
781 region_number = page_number / pages_in_region;
782
783 return !(nvm_module->LOCK.reg & (1 << region_number));
784 }
785
786 ///@cond INTERNAL
787
788 /**
789 * \internal
790 *
791 * \brief Translate fusebit words into struct content.
792 *
793 */
_nvm_translate_raw_fusebits_to_struct(uint32_t * raw_user_row,struct nvm_fusebits * fusebits)794 static void _nvm_translate_raw_fusebits_to_struct (
795 uint32_t *raw_user_row,
796 struct nvm_fusebits *fusebits)
797 {
798
799 fusebits->bootloader_size = (enum nvm_bootloader_size)
800 ((raw_user_row[0] & NVMCTRL_FUSES_BOOTPROT_Msk)
801 >> NVMCTRL_FUSES_BOOTPROT_Pos);
802
803 fusebits->eeprom_size = (enum nvm_eeprom_emulator_size)
804 ((raw_user_row[0] & NVMCTRL_FUSES_EEPROM_SIZE_Msk)
805 >> NVMCTRL_FUSES_EEPROM_SIZE_Pos);
806
807 #if (SAML21) || (SAML22) || (SAMR30)
808 fusebits->bod33_level = (uint8_t)
809 ((raw_user_row[0] & FUSES_BOD33USERLEVEL_Msk)
810 >> FUSES_BOD33USERLEVEL_Pos);
811
812 fusebits->bod33_enable = (bool)
813 (!((raw_user_row[0] & FUSES_BOD33_DIS_Msk)
814 >> FUSES_BOD33_DIS_Pos));
815
816 fusebits->bod33_action = (enum nvm_bod33_action)
817 ((raw_user_row[0] & FUSES_BOD33_ACTION_Msk)
818 >> FUSES_BOD33_ACTION_Pos);
819
820 fusebits->bod33_hysteresis = (bool)
821 ((raw_user_row[1] & FUSES_BOD33_HYST_Msk)
822 >> FUSES_BOD33_HYST_Pos);
823
824 #elif (SAMD20) || (SAMD21) || (SAMR21)|| (SAMDA1) || (SAMD09) || (SAMD10) || (SAMD11) || (SAMHA1)
825 fusebits->bod33_level = (uint8_t)
826 ((raw_user_row[0] & FUSES_BOD33USERLEVEL_Msk)
827 >> FUSES_BOD33USERLEVEL_Pos);
828
829 fusebits->bod33_enable = (bool)
830 ((raw_user_row[0] & FUSES_BOD33_EN_Msk)
831 >> FUSES_BOD33_EN_Pos);
832
833 fusebits->bod33_action = (enum nvm_bod33_action)
834 ((raw_user_row[0] & FUSES_BOD33_ACTION_Msk)
835 >> FUSES_BOD33_ACTION_Pos);
836 fusebits->bod33_hysteresis = (bool)
837 ((raw_user_row[1] & FUSES_BOD33_HYST_Msk)
838 >> FUSES_BOD33_HYST_Pos);
839 #elif (SAMC20) || (SAMC21)
840 fusebits->bodvdd_level = (uint8_t)
841 ((raw_user_row[0] & FUSES_BODVDDUSERLEVEL_Msk)
842 >> FUSES_BODVDDUSERLEVEL_Pos);
843
844 fusebits->bodvdd_enable = (bool)
845 (!((raw_user_row[0] & FUSES_BODVDD_DIS_Msk)
846 >> FUSES_BODVDD_DIS_Pos));
847
848 fusebits->bodvdd_action = (enum nvm_bod33_action)
849 ((raw_user_row[0] & FUSES_BODVDD_ACTION_Msk)
850 >> FUSES_BODVDD_ACTION_Pos);
851
852 fusebits->bodvdd_hysteresis = (raw_user_row[1] & FUSES_BODVDD_HYST_Msk)
853 >> FUSES_BODVDD_HYST_Pos;
854 #endif
855
856 #ifdef FEATURE_BOD12
857
858 #ifndef FUSES_BOD12USERLEVEL_Pos
859 #define FUSES_BOD12USERLEVEL_Pos 17
860 #define FUSES_BOD12USERLEVEL_Msk (0x3Ful << FUSES_BOD12USERLEVEL_Pos)
861 #endif
862 #ifndef FUSES_BOD12_DIS_Pos
863 #define FUSES_BOD12_DIS_Pos 23
864 #define FUSES_BOD12_DIS_Msk (0x1ul << FUSES_BOD12_DIS_Pos)
865 #endif
866 #ifndef FUSES_BOD12_ACTION_Pos
867 #define FUSES_BOD12_ACTION_Pos 24
868 #define FUSES_BOD12_ACTION_Msk (0x3ul << FUSES_BOD12_ACTION_Pos)
869 #endif
870
871 fusebits->bod12_level = (uint8_t)
872 ((raw_user_row[0] & FUSES_BOD12USERLEVEL_Msk)
873 >> FUSES_BOD12USERLEVEL_Pos);
874
875 fusebits->bod12_enable = (bool)
876 (!((raw_user_row[0] & FUSES_BOD12_DIS_Msk)
877 >> FUSES_BOD12_DIS_Pos));
878
879 fusebits->bod12_action = (enum nvm_bod12_action)
880 ((raw_user_row[0] & FUSES_BOD12_ACTION_Msk)
881 >> FUSES_BOD33_ACTION_Pos);
882
883 fusebits->bod12_hysteresis = (bool)
884 ((raw_user_row[1] & FUSES_BOD12_HYST_Msk)
885 >> FUSES_BOD12_HYST_Pos);
886 #endif
887
888 fusebits->wdt_enable = (bool)
889 ((raw_user_row[0] & WDT_FUSES_ENABLE_Msk) >> WDT_FUSES_ENABLE_Pos);
890
891 fusebits->wdt_always_on = (bool)
892 ((raw_user_row[0] & WDT_FUSES_ALWAYSON_Msk) >> WDT_FUSES_ALWAYSON_Pos);
893
894 fusebits->wdt_timeout_period = (uint8_t)
895 ((raw_user_row[0] & WDT_FUSES_PER_Msk) >> WDT_FUSES_PER_Pos);
896
897 #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30)
898 fusebits->wdt_window_timeout = (enum nvm_wdt_window_timeout)
899 ((raw_user_row[1] & WDT_FUSES_WINDOW_Msk) >> WDT_FUSES_WINDOW_Pos);
900 #else
901 /* WDT Windows timout lay between two 32-bit words in the user row. Because only one bit lays in word[0],
902 bits in word[1] must be left sifted by one to make the correct number */
903 fusebits->wdt_window_timeout = (enum nvm_wdt_window_timeout)
904 (((raw_user_row[0] & WDT_FUSES_WINDOW_0_Msk) >> WDT_FUSES_WINDOW_0_Pos) |
905 ((raw_user_row[1] & WDT_FUSES_WINDOW_1_Msk) << 1));
906 #endif
907 fusebits->wdt_early_warning_offset = (enum nvm_wdt_early_warning_offset)
908 ((raw_user_row[1] & WDT_FUSES_EWOFFSET_Msk) >> WDT_FUSES_EWOFFSET_Pos);
909
910 fusebits->wdt_window_mode_enable_at_poweron = (bool)
911 ((raw_user_row[1] & WDT_FUSES_WEN_Msk) >> WDT_FUSES_WEN_Pos);
912
913 fusebits->lockbits = (uint16_t)
914 ((raw_user_row[1] & NVMCTRL_FUSES_REGION_LOCKS_Msk)
915 >> NVMCTRL_FUSES_REGION_LOCKS_Pos);
916
917 }
918
919 ///@endcond
920
921 /**
922 * \brief Get fuses from user row.
923 *
924 * Read out the fuse settings from the user row.
925 *
926 * \param[in] fusebits Pointer to a 64-bit wide memory buffer of type struct nvm_fusebits
927 *
928 * \return Status of read fuses attempt.
929 *
930 * \retval STATUS_OK This function will always return STATUS_OK
931 */
nvm_get_fuses(struct nvm_fusebits * fusebits)932 enum status_code nvm_get_fuses (
933 struct nvm_fusebits *fusebits)
934 {
935 enum status_code error_code = STATUS_OK;
936 uint32_t raw_fusebits[2];
937
938 /* Make sure the module is ready */
939 while (!nvm_is_ready()) {
940 }
941
942 /* Read the fuse settings in the user row, 64 bit */
943 ((uint16_t*)&raw_fusebits)[0] = (uint16_t)NVM_MEMORY[NVMCTRL_USER / 2];
944 ((uint16_t*)&raw_fusebits)[1] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 1];
945 ((uint16_t*)&raw_fusebits)[2] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 2];
946 ((uint16_t*)&raw_fusebits)[3] = (uint16_t)NVM_MEMORY[(NVMCTRL_USER / 2) + 3];
947
948 _nvm_translate_raw_fusebits_to_struct(raw_fusebits, fusebits);
949
950 return error_code;
951 }
952
953 /**
954 * \brief Set fuses from user row.
955 *
956 * Set fuse settings from the user row.
957 *
958 * \note When writing to the user row, the values do not get loaded by the
959 * other modules on the device until a device reset occurs.
960 *
961 * \param[in] fusebits Pointer to a 64-bit wide memory buffer of type struct nvm_fusebits
962 *
963 * \return Status of read fuses attempt.
964 *
965 * \retval STATUS_OK This function will always return STATUS_OK
966 *
967 * \retval STATUS_BUSY If the NVM controller was already busy
968 * executing a command when the new command
969 * was issued
970 * \retval STATUS_ERR_IO If the command was invalid due to memory or
971 * security locking
972 * \retval STATUS_ERR_INVALID_ARG If the given command was invalid or
973 * unsupported
974 * \retval STATUS_ERR_BAD_ADDRESS If the given address was invalid
975 */
976
nvm_set_fuses(struct nvm_fusebits * fb)977 enum status_code nvm_set_fuses(struct nvm_fusebits *fb)
978 {
979 uint32_t fusebits[2];
980 enum status_code error_code = STATUS_OK;
981
982 if (fb == NULL) {
983 return STATUS_ERR_INVALID_ARG;
984 }
985 /* Read the fuse settings in the user row, 64 bit */
986 fusebits[0] = *((uint32_t *)NVMCTRL_AUX0_ADDRESS);
987 fusebits[1] = *(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1);
988
989 /* Set user fuses bit */
990 fusebits[0] &= (~NVMCTRL_FUSES_BOOTPROT_Msk);
991 fusebits[0] |= NVMCTRL_FUSES_BOOTPROT(fb->bootloader_size);
992
993 fusebits[0] &= (~NVMCTRL_FUSES_EEPROM_SIZE_Msk);
994 fusebits[0] |= NVMCTRL_FUSES_EEPROM_SIZE(fb->eeprom_size);
995
996 #if (SAML21) || (SAML22) || (SAMR30)
997 fusebits[0] &= (~FUSES_BOD33USERLEVEL_Msk);
998 fusebits[0] |= FUSES_BOD33USERLEVEL(fb->bod33_level);
999
1000 fusebits[0] &= (~FUSES_BOD33_DIS_Msk);
1001 fusebits[0] |= (!fb->bod33_enable) << FUSES_BOD33_DIS_Pos;
1002
1003 fusebits[0] &= (~FUSES_BOD33_ACTION_Msk);
1004 fusebits[0] |= fb->bod33_action << FUSES_BOD33_ACTION_Pos;
1005
1006 fusebits[1] &= (~FUSES_BOD33_HYST_Msk);
1007 fusebits[1] |= fb->bod33_hysteresis << FUSES_BOD33_HYST_Pos;
1008
1009 #elif (SAMD20) || (SAMD21) || (SAMR21) || (SAMDA1) || (SAMD09) || (SAMD10) || (SAMD11) || (SAMHA1)
1010 fusebits[0] &= (~FUSES_BOD33USERLEVEL_Msk);
1011 fusebits[0] |= FUSES_BOD33USERLEVEL(fb->bod33_level);
1012
1013 fusebits[0] &= (~FUSES_BOD33_EN_Msk);
1014 fusebits[0] |= (fb->bod33_enable) << FUSES_BOD33_EN_Pos;
1015
1016 fusebits[0] &= (~FUSES_BOD33_ACTION_Msk);
1017 fusebits[0] |= fb->bod33_action << FUSES_BOD33_ACTION_Pos;
1018
1019 fusebits[1] &= (~FUSES_BOD33_HYST_Msk);
1020 fusebits[1] |= fb->bod33_hysteresis << FUSES_BOD33_HYST_Pos;
1021
1022 #elif (SAMC20) || (SAMC21)
1023 fusebits[0] &= (~FUSES_BODVDDUSERLEVEL_Msk);
1024 fusebits[0] |= FUSES_BODVDDUSERLEVEL(fb->bodvdd_level);
1025
1026 fusebits[0] &= (~FUSES_BODVDD_DIS_Msk);
1027 fusebits[0] |= (!fb->bodvdd_enable) << FUSES_BODVDD_DIS_Pos;
1028
1029 fusebits[0] &= (~FUSES_BODVDD_ACTION_Msk);
1030 fusebits[0] |= fb->bodvdd_action << FUSES_BODVDD_ACTION_Pos;
1031
1032 fusebits[1] &= (~FUSES_BODVDD_HYST_Msk);
1033 fusebits[1] |= fb->bodvdd_hysteresis << FUSES_BODVDD_HYST_Pos;
1034
1035 #endif
1036
1037 fusebits[0] &= (~WDT_FUSES_ENABLE_Msk);
1038 fusebits[0] |= fb->wdt_enable << WDT_FUSES_ENABLE_Pos;
1039
1040 fusebits[0] &= (~WDT_FUSES_ALWAYSON_Msk);
1041 fusebits[0] |= (fb->wdt_always_on) << WDT_FUSES_ALWAYSON_Pos;
1042
1043 fusebits[0] &= (~WDT_FUSES_PER_Msk);
1044 fusebits[0] |= fb->wdt_timeout_period << WDT_FUSES_PER_Pos;
1045
1046 #if (SAML21) || (SAML22) || (SAMC20) || (SAMC21) || (SAMR30)
1047 fusebits[1] &= (~WDT_FUSES_WINDOW_Msk);
1048 fusebits[1] |= fb->wdt_window_timeout << WDT_FUSES_WINDOW_Pos;
1049 #else
1050 /* WDT Windows timout lay between two 32-bit words in the user row. the last one bit lays in word[0],
1051 and the other bits in word[1] */
1052 fusebits[0] &= (~WDT_FUSES_WINDOW_0_Msk);
1053 fusebits[0] |= (fb->wdt_window_timeout & 0x1) << WDT_FUSES_WINDOW_0_Pos;
1054
1055 fusebits[1] &= (~WDT_FUSES_WINDOW_1_Msk);
1056 fusebits[1] |= (fb->wdt_window_timeout >> 1) << WDT_FUSES_WINDOW_1_Pos;
1057
1058 #endif
1059 fusebits[1] &= (~WDT_FUSES_EWOFFSET_Msk);
1060 fusebits[1] |= fb->wdt_early_warning_offset << WDT_FUSES_EWOFFSET_Pos;
1061
1062 fusebits[1] &= (~WDT_FUSES_WEN_Msk);
1063 fusebits[1] |= fb->wdt_window_mode_enable_at_poweron << WDT_FUSES_WEN_Pos;
1064
1065 fusebits[1] &= (~NVMCTRL_FUSES_REGION_LOCKS_Msk);
1066 fusebits[1] |= fb->lockbits << NVMCTRL_FUSES_REGION_LOCKS_Pos;
1067
1068 #ifdef FEATURE_BOD12
1069
1070 #ifndef FUSES_BOD12USERLEVEL_Pos
1071 #define FUSES_BOD12USERLEVEL_Pos 17
1072 #define FUSES_BOD12USERLEVEL_Msk (0x3Ful << FUSES_BOD12USERLEVEL_Pos)
1073 #endif
1074 #ifndef FUSES_BOD12_DIS_Pos
1075 #define FUSES_BOD12_DIS_Pos 23
1076 #define FUSES_BOD12_DIS_Msk (0x1ul << FUSES_BOD12_DIS_Pos)
1077 #endif
1078 #ifndef FUSES_BOD12_ACTION_Pos
1079 #define FUSES_BOD12_ACTION_Pos 24
1080 #define FUSES_BOD12_ACTION_Msk (0x3ul << FUSES_BOD12_ACTION_Pos)
1081 #endif
1082
1083 fusebits[0] &= (~FUSES_BOD12USERLEVEL_Msk);
1084 fusebits[0] |= ((FUSES_BOD12USERLEVEL_Msk & ((fb->bod12_level) <<
1085 FUSES_BOD12USERLEVEL_Pos)));
1086
1087 fusebits[0] &= (~FUSES_BOD12_DIS_Msk);
1088 fusebits[0] |= (!fb->bod12_enable) << FUSES_BOD12_DIS_Pos;
1089
1090 fusebits[0] &= (~FUSES_BOD12_ACTION_Msk);
1091 fusebits[0] |= fb->bod12_action << FUSES_BOD12_ACTION_Pos;
1092
1093 fusebits[1] &= (~FUSES_BOD12_HYST_Msk);
1094 fusebits[1] |= fb->bod12_hysteresis << FUSES_BOD12_HYST_Pos;
1095 #endif
1096
1097 error_code = nvm_execute_command(NVM_COMMAND_ERASE_AUX_ROW,NVMCTRL_AUX0_ADDRESS,0);
1098 if (error_code != STATUS_OK) {
1099 return error_code;
1100 }
1101
1102 error_code = nvm_execute_command(NVM_COMMAND_PAGE_BUFFER_CLEAR,NVMCTRL_AUX0_ADDRESS,0);
1103 if (error_code != STATUS_OK) {
1104 return error_code;
1105 }
1106
1107 *((uint32_t *)NVMCTRL_AUX0_ADDRESS) = fusebits[0];
1108 *(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1) = fusebits[1];
1109
1110 error_code = nvm_execute_command(NVM_COMMAND_WRITE_AUX_ROW,NVMCTRL_AUX0_ADDRESS,0);
1111 if (error_code != STATUS_OK) {
1112 return error_code;
1113 }
1114
1115 return error_code;
1116 }
1117