1 /*******************************************************************************
\r
2 * (c) Copyright 2011-2013 Microsemi SoC Products Group. All rights reserved.
\r
4 * This source file contains SmartFusion2 eNVM driver code.
\r
6 * SVN $Revision: 5316 $
\r
7 * SVN $Date: 2013-03-24 12:33:15 +0000 (Sun, 24 Mar 2013) $
\r
10 #include "../../CMSIS/m2sxxx.h"
\r
11 #include "../../CMSIS/mss_assert.h"
\r
12 #include "../../CMSIS/system_m2sxxx.h"
\r
13 #include "mss_nvm.h"
\r
19 /**************************************************************************/
\r
20 /* Preprocessor definitions */
\r
21 /**************************************************************************/
\r
22 /* eNVM command codes */
\r
23 #define PROG_ADS 0x08000000u /* One shot program with data in WD */
\r
24 #define VERIFY_ADS 0x10000000u /* One shot verification with data in WD */
\r
25 #define USER_UNLOCK 0x13000000u /* User unlock */
\r
27 #define BITS_PER_PAGE 1024u /* Number of bits per page */
\r
28 #define BYTES_PER_PAGE (BITS_PER_PAGE / 8u) /* Number of bytes per page */
\r
30 #define NVM_OFFSET_SIGNIFICANT_BITS 0x0007FFFFu
\r
31 #define NVM1_BOTTOM_OFFSET 0x00040000u
\r
32 #define NVM1_TOP_OFFSET 0x0007FFFFu
\r
34 #define NVM_BASE_ADDRESS 0x60000000u
\r
36 #define PAGE_ADDR_MASK 0xFFFFFF80u
\r
38 #define BLOCK1_FIRST_WORD_OFFSET 0x00010000u
\r
40 #define WD_WORD_SIZE 32u
\r
42 #define NB_OF_BYTES_IN_A_WORD 4u
\r
44 #define WRITE_ERROR_MASK (MSS_NVM_VERIFY_FAIL | \
\r
45 MSS_NVM_EVERIFY_FAIL | \
\r
46 MSS_NVM_WVERIFY_FAIL | \
\r
47 MSS_NVM_PEFAIL_LOCK | \
\r
48 MSS_NVM_WRCNT_OVER | \
\r
51 /*******************************************************************************
\r
52 * Combined status definitions
\r
53 * Below definitions should be used to decoded the bit encoded status returned
\r
54 * by the function MSS_NVM_get_status().
\r
56 #define MSS_NVM_BUSY_B (1u) /* NVM is performing an internal operation */
\r
57 #define MSS_NVM_VERIFY_FAIL ((uint32_t)1 << 1u) /* NVM verify operation failed */
\r
58 #define MSS_NVM_EVERIFY_FAIL ((uint32_t)1 << 2u) /* NVM erase verify operation failed */
\r
59 #define MSS_NVM_WVERIFY_FAIL ((uint32_t)1 << 3u) /* NVM write verify operation failed */
\r
60 #define MSS_NVM_PEFAIL_LOCK ((uint32_t)1 << 4u) /* NVM program / erase operation failed due to page lock */
\r
61 #define MSS_NVM_WRCNT_OVER ((uint32_t)1 << 5u) /* NVM write count overflowed */
\r
62 #define MSS_NVM_WR_DENIED ((uint32_t)1 << 18u) /* NVM write is denied due to protection */
\r
64 /*******************************************************************************
\r
67 #define NVM_BLOCK_0 0u
\r
68 #define NVM_BLOCK_1 1u
\r
70 /*******************************************************************************
\r
73 #define MAX_512K_OFFSET 0x00080000u
\r
75 /**************************************************************************/
\r
76 /* Global data definitions */
\r
77 /**************************************************************************/
\r
78 /**************************************************************************//**
\r
79 * Look-up table for NVM blocks.
\r
81 static NVM_TypeDef * const g_nvm[] =
\r
87 static NVM32_TypeDef * const g_nvm32[] =
\r
89 (NVM32_TypeDef *)ENVM1_BASE ,
\r
90 (NVM32_TypeDef *)ENVM2_BASE
\r
93 /**************************************************************************/
\r
94 /* Private function declarations */
\r
95 /**************************************************************************/
\r
96 static nvm_status_t request_nvm_access
\r
98 uint32_t nvm_block_id
\r
101 static nvm_status_t get_ctrl_access
\r
103 uint32_t nvm_offset,
\r
107 static void release_ctrl_access(void);
\r
109 static uint32_t get_remaining_page_length
\r
115 static void fill_wd_buffer
\r
117 const uint8_t * p_data,
\r
127 const uint8_t * pidata,
\r
129 uint32_t lock_page,
\r
130 uint32_t * p_status
\r
139 static nvm_status_t get_error_code(uint32_t nvm_hw_status);
\r
141 /**************************************************************************/
\r
142 /* Public function definitions */
\r
143 /**************************************************************************/
\r
145 /**************************************************************************//**
\r
146 * See mss_nvm.h for details of how to use this function.
\r
151 uint32_t start_addr,
\r
152 const uint8_t * pidata,
\r
157 nvm_status_t status;
\r
158 uint32_t nvm_offset;
\r
159 uint32_t device_version;
\r
162 * Prevent pages being locked for silicon versions which do not allow
\r
163 * locked pages to be unlocked.
\r
165 device_version = SYSREG->DEVICE_VERSION;
\r
166 if((0x0000F802u == device_version) || (0x0001F802u == device_version))
\r
168 lock_page = NVM_DO_NOT_LOCK_PAGE;
\r
171 /* Ignore upper address bits to ignore remapping setting. */
\r
172 nvm_offset = start_addr & NVM_OFFSET_SIGNIFICANT_BITS; /* Ignore remapping. */
\r
174 /* Check against attempt to write data larger than eNVM. */
\r
175 ASSERT((nvm_offset + length) < MAX_512K_OFFSET);
\r
176 if((nvm_offset + length) < MAX_512K_OFFSET)
\r
178 /* Gain exclusive access to eNVM controller */
\r
179 status = get_ctrl_access(nvm_offset, length);
\r
181 /* Write eNVM one page at a time. */
\r
182 if(NVM_SUCCESS == status)
\r
184 uint32_t remaining_length = length;
\r
186 while((remaining_length > 0u) && (NVM_SUCCESS == status))
\r
188 uint32_t length_written;
\r
189 uint32_t nvm_hw_status = 0u;
\r
191 length_written = write_nvm(start_addr + (length - remaining_length),
\r
192 &pidata[length - remaining_length],
\r
197 if(0u == length_written)
\r
199 status = get_error_code(nvm_hw_status);
\r
201 else if(remaining_length > length_written)
\r
203 remaining_length -= length_written;
\r
207 remaining_length = 0u;
\r
211 /* Release eNVM controllers so that other masters can gain access to it. */
\r
212 release_ctrl_access();
\r
217 status = NVM_INVALID_PARAMETER;
\r
223 /**************************************************************************//**
\r
224 Generate error code based on NVM status value.
\r
226 The hardware nvm status passed as parameter is expected to be masked using the
\r
228 (MSS_NVM_VERIFY_FAIL | \
\r
229 MSS_NVM_EVERIFY_FAIL | \
\r
230 MSS_NVM_WVERIFY_FAIL | \
\r
231 MSS_NVM_PEFAIL_LOCK | \
\r
232 MSS_NVM_WRCNT_OVER | \
\r
236 static nvm_status_t get_error_code(uint32_t nvm_hw_status)
\r
238 nvm_status_t status;
\r
239 if(nvm_hw_status & MSS_NVM_WR_DENIED)
\r
241 status = NVM_PROTECTION_ERROR;
\r
243 else if(nvm_hw_status & MSS_NVM_WRCNT_OVER)
\r
245 status = NVM_WRITE_THRESHOLD_ERROR;
\r
247 else if(nvm_hw_status & MSS_NVM_PEFAIL_LOCK)
\r
249 status = NVM_PAGE_LOCK_ERROR;
\r
253 status = NVM_VERIFY_FAILURE;
\r
259 /**************************************************************************//**
\r
260 * See mss_nvm.h for details of how to use this function.
\r
265 uint32_t start_addr,
\r
269 nvm_status_t status;
\r
270 uint32_t nvm_offset;
\r
271 uint32_t first_page;
\r
272 uint32_t last_page;
\r
273 uint32_t current_page;
\r
274 uint32_t current_offset;
\r
276 /* Ignore upper address bits to ignore remapping setting. */
\r
277 nvm_offset = start_addr & NVM_OFFSET_SIGNIFICANT_BITS; /* Ignore remapping. */
\r
279 /* Check against attempt to write data larger than eNVM. */
\r
280 ASSERT((nvm_offset + length) < MAX_512K_OFFSET);
\r
281 if((nvm_offset + length) < MAX_512K_OFFSET)
\r
283 current_offset = nvm_offset;
\r
285 first_page = nvm_offset / BYTES_PER_PAGE;
\r
286 last_page = (nvm_offset + length) / BYTES_PER_PAGE;
\r
288 /* Gain exclusive access to eNVM controller */
\r
289 status = get_ctrl_access(nvm_offset, length);
\r
291 /* Unlock eNVM one page at a time. */
\r
292 if(NVM_SUCCESS == status)
\r
296 uint32_t first_word;
\r
297 uint32_t word_offset;
\r
298 uint32_t * p_nvm32;
\r
301 p_nvm32 = (uint32_t *)NVM_BASE_ADDRESS;
\r
303 first_word = nvm_offset / 4u;
\r
304 word_offset = first_word;
\r
306 for(current_page = first_page;
\r
307 (current_page <= last_page) && (NVM_SUCCESS == status);
\r
310 uint32_t ctrl_status;
\r
312 if(word_offset >= BLOCK1_FIRST_WORD_OFFSET)
\r
314 block = NVM_BLOCK_1;
\r
318 block = NVM_BLOCK_0;
\r
321 for(inc = 0u; inc < WD_WORD_SIZE; ++inc)
\r
323 g_nvm32[block]->WD[inc] = p_nvm32[word_offset];
\r
327 g_nvm[block]->PAGE_LOCK = NVM_DO_NOT_LOCK_PAGE;
\r
328 g_nvm[block]->CMD = USER_UNLOCK;
\r
330 /* Issue program command */
\r
331 g_nvm[block]->CMD = PROG_ADS | (current_offset & PAGE_ADDR_MASK);
\r
332 current_offset += BYTES_PER_PAGE;
\r
334 /* Wait for NVM to become ready. */
\r
335 ctrl_status = wait_nvm_ready(block);
\r
337 /* Check for errors. */
\r
338 errors = ctrl_status & WRITE_ERROR_MASK;
\r
341 uint32_t nvm_hw_status;
\r
342 nvm_hw_status = g_nvm[block]->STATUS;
\r
343 status = get_error_code(nvm_hw_status);
\r
347 /* Release eNVM controllers so that other masters can gain access to it. */
\r
348 release_ctrl_access();
\r
353 status = NVM_INVALID_PARAMETER;
\r
359 /**************************************************************************/
\r
360 /* Private function definitions */
\r
361 /**************************************************************************/
\r
363 /**************************************************************************//**
\r
366 #define ACCESS_REQUEST_TIMEOUT 0x00800000u
\r
367 #define REQUEST_NVM_ACCESS 0x01u
\r
368 #define CORTEX_M3_ACCESS_GRANTED 0x05u
\r
370 static uint8_t g_envm_ctrl_locks = 0x00u;
\r
372 static nvm_status_t
\r
375 uint32_t nvm_block_id
\r
378 nvm_status_t status = NVM_SUCCESS;
\r
379 volatile uint32_t timeout_counter;
\r
383 * Use the SystemCoreClock frequency to compute a delay counter value giving
\r
384 * us a delay in the 500ms range. This is a very approximate delay.
\r
386 timeout_counter = SystemCoreClock / 16u;
\r
389 * Gain access to eNVM controller.
\r
392 g_nvm[nvm_block_id]->REQ_ACCESS = REQUEST_NVM_ACCESS;
\r
393 access = g_nvm[nvm_block_id]->REQ_ACCESS;
\r
394 if(access != CORTEX_M3_ACCESS_GRANTED)
\r
397 * Time out if another AHB master locked access to eNVM to prevent
\r
398 * the Cortex-M3 from locking up on eNVM write if some other part
\r
399 * of the system fails from releasing the eNVM.
\r
402 if(0u == timeout_counter)
\r
404 status = NVM_IN_USE_BY_OTHER_MASTER;
\r
407 } while((access != CORTEX_M3_ACCESS_GRANTED) && (NVM_SUCCESS == status));
\r
410 * Mark controller as locked if successful so that it will be unlocked by a
\r
411 * call to release_ctrl_access.
\r
413 if(NVM_SUCCESS == status)
\r
415 g_envm_ctrl_locks |= (uint8_t)((uint32_t)0x01 << nvm_block_id);
\r
422 /**************************************************************************//**
\r
425 static nvm_status_t
\r
428 uint32_t nvm_offset,
\r
432 nvm_status_t access_req_status;
\r
435 * Gain access to eNVM controller(s).
\r
437 if(nvm_offset < NVM1_BOTTOM_OFFSET)
\r
439 access_req_status = request_nvm_access(NVM_BLOCK_0);
\r
440 if(NVM_SUCCESS == access_req_status)
\r
442 uint32_t last_offset;
\r
443 last_offset = nvm_offset + length;
\r
444 if(last_offset >= NVM1_BOTTOM_OFFSET)
\r
446 access_req_status = request_nvm_access(NVM_BLOCK_1);
\r
447 if(NVM_IN_USE_BY_OTHER_MASTER == access_req_status)
\r
449 release_ctrl_access();
\r
456 access_req_status = request_nvm_access(NVM_BLOCK_1);
\r
459 return access_req_status;
\r
462 /**************************************************************************//**
\r
463 * Release access to eNVM controllers.
\r
465 #define NVM_BLOCK_0_LOCK_MASK 0x01u
\r
466 #define NVM_BLOCK_1_LOCK_MASK 0x02u
\r
468 static void release_ctrl_access(void)
\r
470 uint8_t block_locked;
\r
472 block_locked = g_envm_ctrl_locks & NVM_BLOCK_0_LOCK_MASK;
\r
475 g_nvm[NVM_BLOCK_0]->REQ_ACCESS = 0x00u;
\r
476 g_envm_ctrl_locks &= ~NVM_BLOCK_0_LOCK_MASK;
\r
479 block_locked = g_envm_ctrl_locks & NVM_BLOCK_1_LOCK_MASK;
\r
482 g_nvm[NVM_BLOCK_1]->REQ_ACCESS = 0x00u;
\r
483 g_envm_ctrl_locks &= ~NVM_BLOCK_1_LOCK_MASK;
\r
487 /**************************************************************************//**
\r
490 static uint32_t wait_nvm_ready(uint32_t block)
\r
492 volatile uint32_t ctrl_status;
\r
493 uint32_t ctrl_ready;
\r
497 * Wait for NVM to become ready.
\r
498 * We must ensure that we can read the ready bit set to 1 twice before we
\r
499 * can assume that the other status bits are valid. See SmartFusion2 errata.
\r
501 for(inc = 0u; inc < 2u; ++inc)
\r
504 ctrl_status = g_nvm[block]->STATUS;
\r
505 ctrl_ready = ctrl_status & MSS_NVM_BUSY_B;
\r
506 } while(0u == ctrl_ready);
\r
509 return ctrl_status;
\r
512 /**************************************************************************//**
\r
513 Write as much data as will fit into the eNVM page corresponding to the
\r
514 address "addr" passed as parameter. Return the number of bytes written into
\r
516 In case of error, return the content of the eNVM controller status register
\r
517 into the 32-bit word pointed to by p_status.
\r
523 const uint8_t * pidata,
\r
525 uint32_t lock_page,
\r
526 uint32_t * p_status
\r
529 uint32_t length_written;
\r
534 offset = addr & NVM_OFFSET_SIGNIFICANT_BITS; /* Ignore remapping. */
\r
536 ASSERT(offset <= NVM1_TOP_OFFSET);
\r
538 /* Adjust length to fit within one page. */
\r
539 length_written = get_remaining_page_length(offset, length);
\r
541 if(offset <= NVM1_TOP_OFFSET)
\r
544 volatile uint32_t ctrl_status;
\r
547 if(offset < NVM1_BOTTOM_OFFSET)
\r
549 block = NVM_BLOCK_0;
\r
553 block = NVM_BLOCK_1;
\r
554 offset = offset - NVM1_BOTTOM_OFFSET;
\r
557 fill_wd_buffer(pidata, length_written, block, offset);
\r
559 /* Set requested locking option. */
\r
560 g_nvm[block]->PAGE_LOCK = lock_page;
\r
562 /* Issue program command */
\r
563 g_nvm[block]->CMD = PROG_ADS | (offset & PAGE_ADDR_MASK);
\r
565 /* Wait for NVM to become ready. */
\r
566 ctrl_status = wait_nvm_ready(block);
\r
568 /* Check for errors. */
\r
569 errors = ctrl_status & WRITE_ERROR_MASK;
\r
572 /* Signal that an error occured by returning 0 a a number of bytes written. */
\r
573 length_written = 0u;
\r
574 *p_status = g_nvm[block]->STATUS;
\r
578 /* Perform a verify. */
\r
579 g_nvm[block]->CMD = VERIFY_ADS | (offset & PAGE_ADDR_MASK);
\r
580 /* Wait for NVM to become ready. */
\r
581 ctrl_status = wait_nvm_ready(block);
\r
583 /* Check for errors. */
\r
584 errors = ctrl_status & WRITE_ERROR_MASK;
\r
587 /* Signal that an error occured by returning 0 a a number of bytes written. */
\r
588 length_written = 0u;
\r
589 *p_status = g_nvm[block]->STATUS;
\r
594 return length_written;
\r
597 /*******************************************************************************
\r
598 Return the number of bytes between the offset location and the end of the page
\r
599 containing the first offset location. This tells us how many actual bytes can
\r
600 be programmed with a single ProgramADS command.
\r
601 This also tells us if we are programming a full page. If the return value is
\r
602 equal to BYTES_PER_PAGE then we will be pragramming an entire NVM page.
\r
603 Alternatively, this function returning a value other than BYTES_PER_PAGE
\r
604 indicates that the WD[] buffer will have to be seeded with the existing
\r
605 content of the eNVM before copying the data to program to eNVM into the WD[]
\r
608 static uint32_t get_remaining_page_length
\r
614 uint32_t start_page_plus_one;
\r
615 uint32_t last_page;
\r
617 start_page_plus_one = (offset / BYTES_PER_PAGE) + 1u;
\r
618 last_page = (offset + length) / BYTES_PER_PAGE;
\r
619 if(last_page >= start_page_plus_one)
\r
621 length = BYTES_PER_PAGE - (offset % BYTES_PER_PAGE);
\r
627 /**************************************************************************//**
\r
630 static void fill_wd_buffer
\r
632 const uint8_t * p_data,
\r
639 uint32_t wd_offset;
\r
641 if(length != BYTES_PER_PAGE)
\r
643 uint32_t * p_nvm32;
\r
644 uint32_t nb_non_written_words;
\r
645 uint32_t first_non_written_word;
\r
647 * Fill beginning of WD[] with current content of NVM page that must not
\r
650 p_nvm32 = (uint32_t *)((NVM_BASE_ADDRESS + offset) & PAGE_ADDR_MASK);
\r
651 nb_non_written_words = (offset % BYTES_PER_PAGE) / NB_OF_BYTES_IN_A_WORD;
\r
652 if((offset % NB_OF_BYTES_IN_A_WORD) > 0u)
\r
654 ++nb_non_written_words;
\r
656 for(inc = 0u; (inc < WD_WORD_SIZE) && (inc < nb_non_written_words); ++inc)
\r
658 g_nvm32[block]->WD[inc] = p_nvm32[inc];
\r
662 * Fill end of WD[] with current content of NVM page that must not be
\r
665 first_non_written_word = ((offset + length) % BYTES_PER_PAGE) / NB_OF_BYTES_IN_A_WORD;
\r
666 nb_non_written_words = (BYTES_PER_PAGE / NB_OF_BYTES_IN_A_WORD) - first_non_written_word;
\r
668 for(inc = 0u; inc < nb_non_written_words; ++inc)
\r
670 g_nvm32[block]->WD[first_non_written_word + inc] = p_nvm32[first_non_written_word + inc];
\r
675 * Fill WD[] with data requested to be written into NVM.
\r
677 wd_offset = offset % BYTES_PER_PAGE;
\r
678 for(inc = 0u; inc < length; ++inc)
\r
680 g_nvm[block]->WD[wd_offset + inc] = p_data[inc];
\r
688 /******************************** END OF FILE ******************************/
\r