]> git.sur5r.net Git - freertos/blob - Demo/CORTEX_LM3S811_GCC/hw_include/flash.c
Start to re-arrange files to include FreeRTOS+ in main download.
[freertos] / Demo / CORTEX_LM3S811_GCC / hw_include / flash.c
1 //*****************************************************************************\r
2 //\r
3 // flash.c - Driver for programming the on-chip flash.\r
4 //\r
5 // Copyright (c) 2005,2006 Luminary Micro, Inc.  All rights reserved.\r
6 //\r
7 // Software License Agreement\r
8 //\r
9 // Luminary Micro, Inc. (LMI) is supplying this software for use solely and\r
10 // exclusively on LMI's Stellaris Family of microcontroller products.\r
11 //\r
12 // The software is owned by LMI and/or its suppliers, and is protected under\r
13 // applicable copyright laws.  All rights are reserved.  Any use in violation\r
14 // of the foregoing restrictions may subject the user to criminal sanctions\r
15 // under applicable laws, as well as to civil liability for the breach of the\r
16 // terms and conditions of this license.\r
17 //\r
18 // THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED\r
19 // OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF\r
20 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.\r
21 // LMI SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR\r
22 // CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.\r
23 //\r
24 // This is part of revision 991 of the Stellaris Driver Library.\r
25 //\r
26 //*****************************************************************************\r
27 \r
28 //*****************************************************************************\r
29 //\r
30 //! \addtogroup flash_api\r
31 //! @{\r
32 //\r
33 //*****************************************************************************\r
34 \r
35 #include "../hw_flash.h"\r
36 #include "../hw_ints.h"\r
37 #include "../hw_memmap.h"\r
38 #include "../hw_sysctl.h"\r
39 #include "../hw_types.h"\r
40 #include "debug.h"\r
41 #include "flash.h"\r
42 #include "interrupt.h"\r
43 \r
44 //*****************************************************************************\r
45 //\r
46 //! Gets the number of processor clocks per micro-second.\r
47 //!\r
48 //! This function returns the number of clocks per micro-second, as presently\r
49 //! known by the flash controller.\r
50 //!\r
51 //! \return Returns the number of processor clocks per micro-second.\r
52 //\r
53 //*****************************************************************************\r
54 #if defined(GROUP_usecget) || defined(BUILD_ALL) || defined(DOXYGEN)\r
55 unsigned long\r
56 FlashUsecGet(void)\r
57 {\r
58     //\r
59     // Return the number of clocks per micro-second.\r
60     //\r
61     return(HWREG(FLASH_USECRL) + 1);\r
62 }\r
63 #endif\r
64 \r
65 //*****************************************************************************\r
66 //\r
67 //! Sets the number of processor clocks per micro-second.\r
68 //!\r
69 //! \param ulClocks is the number of processor clocks per micro-second.\r
70 //!\r
71 //! This function is used to tell the flash controller the number of processor\r
72 //! clocks per micro-second.  This value must be programmed correctly or the\r
73 //! flash most likely will not program correctly; it has no affect on reading\r
74 //! flash.\r
75 //!\r
76 //! \return None.\r
77 //\r
78 //*****************************************************************************\r
79 #if defined(GROUP_usecset) || defined(BUILD_ALL) || defined(DOXYGEN)\r
80 void\r
81 FlashUsecSet(unsigned long ulClocks)\r
82 {\r
83     //\r
84     // Set the number of clocks per micro-second.\r
85     //\r
86     HWREG(FLASH_USECRL) = ulClocks - 1;\r
87 }\r
88 #endif\r
89 \r
90 //*****************************************************************************\r
91 //\r
92 //! Erases a block of flash.\r
93 //!\r
94 //! \param ulAddress is the start address of the flash block to be erased.\r
95 //!\r
96 //! This function will erase a 1 kB block of the on-chip flash.  After erasing,\r
97 //! the block will be filled with 0xFF bytes.  Read-only and execute-only\r
98 //! blocks cannot be erased.\r
99 //!\r
100 //! This function will not return until the block has been erased.\r
101 //!\r
102 //! \return Returns 0 on success, or -1 if an invalid block address was\r
103 //! specified or the block is write-protected.\r
104 //\r
105 //*****************************************************************************\r
106 #if defined(GROUP_erase) || defined(BUILD_ALL) || defined(DOXYGEN)\r
107 long\r
108 FlashErase(unsigned long ulAddress)\r
109 {\r
110     //\r
111     // Check the arguments.\r
112     //\r
113     ASSERT(!(ulAddress & (FLASH_ERASE_SIZE - 1)));\r
114 \r
115     //\r
116     // Clear the flash access interrupt.\r
117     //\r
118     HWREG(FLASH_FCMISC) = FLASH_FCMISC_ACCESS;\r
119 \r
120     //\r
121     // Erase the block.\r
122     //\r
123     HWREG(FLASH_FMA) = ulAddress;\r
124     HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_ERASE;\r
125 \r
126     //\r
127     // Wait until the word has been programmed.\r
128     //\r
129     while(HWREG(FLASH_FMC) & FLASH_FMC_ERASE)\r
130     {\r
131     }\r
132 \r
133     //\r
134     // Return an error if an access violation occurred.\r
135     //\r
136     if(HWREG(FLASH_FCRIS) & FLASH_FCRIS_ACCESS)\r
137     {\r
138         return(-1);\r
139     }\r
140 \r
141     //\r
142     // Success.\r
143     //\r
144     return(0);\r
145 }\r
146 #endif\r
147 \r
148 //*****************************************************************************\r
149 //\r
150 //! Programs flash.\r
151 //!\r
152 //! \param pulData is a pointer to the data to be programmed.\r
153 //! \param ulAddress is the starting address in flash to be programmed.  Must\r
154 //! be a multiple of four.\r
155 //! \param ulCount is the number of bytes to be programmed.  Must be a multiple\r
156 //! of four.\r
157 //!\r
158 //! This function will program a sequence of words into the on-chip flash.\r
159 //! Programming each location consists of the result of an AND operation\r
160 //! of the new data and the existing data; in other words bits that contain\r
161 //! 1 can remain 1 or be changed to 0, but bits that are 0 cannot be changed\r
162 //! to 1.  Therefore, a word can be programmed multiple times as long as these\r
163 //! rules are followed; if a program operation attempts to change a 0 bit to\r
164 //! a 1 bit, that bit will not have its value changed.\r
165 //!\r
166 //! Since the flash is programmed one word at a time, the starting address and\r
167 //! byte count must both be multiples of four.  It is up to the caller to\r
168 //! verify the programmed contents, if such verification is required.\r
169 //!\r
170 //! This function will not return until the data has been programmed.\r
171 //!\r
172 //! \return Returns 0 on success, or -1 if a programming error is encountered.\r
173 //\r
174 //*****************************************************************************\r
175 #if defined(GROUP_program) || defined(BUILD_ALL) || defined(DOXYGEN)\r
176 long\r
177 FlashProgram(unsigned long *pulData, unsigned long ulAddress,\r
178              unsigned long ulCount)\r
179 {\r
180     //\r
181     // Check the arguments.\r
182     //\r
183     ASSERT(!(ulAddress & 3));\r
184     ASSERT(!(ulCount & 3));\r
185 \r
186     //\r
187     // Clear the flash access interrupt.\r
188     //\r
189     HWREG(FLASH_FCMISC) = FLASH_FCMISC_ACCESS;\r
190 \r
191     //\r
192     // Loop over the words to be programmed.\r
193     //\r
194     while(ulCount)\r
195     {\r
196         //\r
197         // Program the next word.\r
198         //\r
199         HWREG(FLASH_FMA) = ulAddress;\r
200         HWREG(FLASH_FMD) = *pulData;\r
201         HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_WRITE;\r
202 \r
203         //\r
204         // Wait until the word has been programmed.\r
205         //\r
206         while(HWREG(FLASH_FMC) & FLASH_FMC_WRITE)\r
207         {\r
208         }\r
209 \r
210         //\r
211         // Increment to the next word.\r
212         //\r
213         pulData++;\r
214         ulAddress += 4;\r
215         ulCount -= 4;\r
216     }\r
217 \r
218     //\r
219     // Return an error if an access violation occurred.\r
220     //\r
221     if(HWREG(FLASH_FCRIS) & FLASH_FCRIS_ACCESS)\r
222     {\r
223         return(-1);\r
224     }\r
225 \r
226     //\r
227     // Success.\r
228     //\r
229     return(0);\r
230 }\r
231 #endif\r
232 \r
233 //*****************************************************************************\r
234 //\r
235 //! Gets the protection setting for a block of flash.\r
236 //!\r
237 //! \param ulAddress is the start address of the flash block to be queried.\r
238 //!\r
239 //! This function will get the current protection for the specified 2 kB block\r
240 //! of flash.  Each block can be read/write, read-only, or execute-only.\r
241 //! Read/write blocks can be read, executed, erased, and programmed.  Read-only\r
242 //! blocks can be read and executed.  Execute-only blocks can only be executed;\r
243 //! processor and debugger data reads are not allowed.\r
244 //!\r
245 //! \return Returns the protection setting for this block.  See\r
246 //! FlashProtectSet() for possible values.\r
247 //\r
248 //*****************************************************************************\r
249 #if defined(GROUP_protectget) || defined(BUILD_ALL) || defined(DOXYGEN)\r
250 tFlashProtection\r
251 FlashProtectGet(unsigned long ulAddress)\r
252 {\r
253     unsigned long ulFMPRE, ulFMPPE;\r
254 \r
255     //\r
256     // Check the argument.\r
257     //\r
258     ASSERT(!(ulAddress & (FLASH_PROTECT_SIZE - 1)));\r
259 \r
260     //\r
261     // Read the flash protection register and get the bits that apply to the\r
262     // specified block.\r
263     //\r
264     ulFMPRE = HWREG(FLASH_FMPRE);\r
265     ulFMPPE = HWREG(FLASH_FMPPE);\r
266     switch((((ulFMPRE >> (ulAddress / FLASH_PROTECT_SIZE)) &\r
267              FLASH_FMP_BLOCK_0) << 1) |\r
268            ((ulFMPPE >> (ulAddress / FLASH_PROTECT_SIZE)) & FLASH_FMP_BLOCK_0))\r
269     {\r
270         //\r
271         // This block is marked as execute only (i.e. it can not be erased or\r
272         // programmed, and the only reads allowed are via the instruction fecth\r
273         // interface).\r
274         //\r
275         case 0:\r
276         case 1:\r
277         {\r
278             return(FlashExecuteOnly);\r
279         }\r
280 \r
281         //\r
282         // This block is marked as read only (i.e. it can not be erased or\r
283         // programmed).\r
284         //\r
285         case 2:\r
286         {\r
287             return(FlashReadOnly);\r
288         }\r
289 \r
290         //\r
291         // This block is read/write; it can be read, erased, and programmed.\r
292         //\r
293         case 3:\r
294         default:\r
295         {\r
296             return(FlashReadWrite);\r
297         }\r
298     }\r
299 }\r
300 #endif\r
301 \r
302 //*****************************************************************************\r
303 //\r
304 //! Sets the protection setting for a block of flash.\r
305 //!\r
306 //! \param ulAddress is the start address of the flash block to be protected.\r
307 //! \param eProtect is the protection to be applied to the block.  Can be one\r
308 //! of \b FlashReadWrite, \b FlashReadOnly, or \b FlashExecuteOnly.\r
309 //!\r
310 //! This function will set the protection for the specified 2 kB block of\r
311 //! flash.  Blocks which are read/write can be made read-only or execute-only.\r
312 //! Blocks which are read-only can be made execute-only.  Blocks which are\r
313 //! execute-only cannot have their protection modified.  Attempts to make the\r
314 //! block protection less stringent (i.e. read-only to read/write) will result\r
315 //! in a failure (and be prevented by the hardware).\r
316 //!\r
317 //! Changes to the flash protection are maintained only until the next reset.\r
318 //! This allows the application to be executed in the desired flash protection\r
319 //! environment to check for inappropriate flash access (via the flash\r
320 //! interrupt).  To make the flash protection permanent, use the\r
321 //! FlashProtectSave() function.\r
322 //!\r
323 //! \return Returns 0 on success, or -1 if an invalid address or an invalid\r
324 //! protection was specified.\r
325 //\r
326 //*****************************************************************************\r
327 #if defined(GROUP_protectset) || defined(BUILD_ALL) || defined(DOXYGEN)\r
328 long\r
329 FlashProtectSet(unsigned long ulAddress, tFlashProtection eProtect)\r
330 {\r
331     unsigned long ulProtectRE, ulProtectPE;\r
332 \r
333     //\r
334     // Check the argument.\r
335     //\r
336     ASSERT(!(ulAddress & (FLASH_PROTECT_SIZE - 1)));\r
337     ASSERT((eProtect == FlashReadWrite) || (eProtect == FlashReadOnly) ||\r
338            (eProtect == FlashExecuteOnly));\r
339 \r
340     //\r
341     // Convert the address into a block number.\r
342     //\r
343     ulAddress /= FLASH_PROTECT_SIZE;\r
344 \r
345     //\r
346     // Get the current protection.\r
347     //\r
348     ulProtectRE = HWREG(FLASH_FMPRE);\r
349     ulProtectPE = HWREG(FLASH_FMPPE);\r
350 \r
351     //\r
352     // Set the protection based on the requested proection.\r
353     //\r
354     switch(eProtect)\r
355     {\r
356         //\r
357         // Make this block execute only.\r
358         //\r
359         case FlashExecuteOnly:\r
360         {\r
361             //\r
362             // Turn off the read and program bits for this block.\r
363             //\r
364             ulProtectRE &= ~(FLASH_FMP_BLOCK_0 << ulAddress);\r
365             ulProtectPE &= ~(FLASH_FMP_BLOCK_0 << ulAddress);\r
366 \r
367             //\r
368             // We're done handling this protection.\r
369             //\r
370             break;\r
371         }\r
372 \r
373         //\r
374         // Make this block read only.\r
375         //\r
376         case FlashReadOnly:\r
377         {\r
378             //\r
379             // The block can not be made read only if it is execute only.\r
380             //\r
381             if(((ulProtectRE >> ulAddress) & FLASH_FMP_BLOCK_0) !=\r
382                FLASH_FMP_BLOCK_0)\r
383             {\r
384                 return(-1);\r
385             }\r
386 \r
387             //\r
388             // Make this block read only.\r
389             //\r
390             ulProtectPE &= ~(FLASH_FMP_BLOCK_0 << ulAddress);\r
391 \r
392             //\r
393             // We're done handling this protection.\r
394             //\r
395             break;\r
396         }\r
397 \r
398         //\r
399         // Make this block read/write.\r
400         //\r
401         case FlashReadWrite:\r
402         default:\r
403         {\r
404             //\r
405             // The block can not be made read/write if it is not already\r
406             // read/write.\r
407             //\r
408             if((((ulProtectRE >> ulAddress) & FLASH_FMP_BLOCK_0) !=\r
409                 FLASH_FMP_BLOCK_0) ||\r
410                (((ulProtectPE >> ulAddress) & FLASH_FMP_BLOCK_0) !=\r
411                 FLASH_FMP_BLOCK_0))\r
412             {\r
413                 return(-1);\r
414             }\r
415 \r
416             //\r
417             // The block is already read/write, so there is nothing to do.\r
418             //\r
419             return(0);\r
420         }\r
421     }\r
422 \r
423     //\r
424     // Set the new protection.\r
425     //\r
426     HWREG(FLASH_FMPRE) = ulProtectRE;\r
427     HWREG(FLASH_FMPPE) = ulProtectPE;\r
428 \r
429     //\r
430     // Success.\r
431     //\r
432     return(0);\r
433 }\r
434 #endif\r
435 \r
436 //*****************************************************************************\r
437 //\r
438 //! Saves the flash protection settings.\r
439 //!\r
440 //! This function will make the currently programmed flash protection settings\r
441 //! permanent.  This is a non-reversible operation; a chip reset or power cycle\r
442 //! will not change the flash protection.\r
443 //!\r
444 //! This function will not return until the protection has been saved.\r
445 //!\r
446 //! \return Returns 0 on success, or -1 if a hardware error is encountered.\r
447 //\r
448 //*****************************************************************************\r
449 #if defined(GROUP_protectsave) || defined(BUILD_ALL) || defined(DOXYGEN)\r
450 long\r
451 FlashProtectSave(void)\r
452 {\r
453     //\r
454     // Tell the flash controller to write the flash read protection register.\r
455     //\r
456     HWREG(FLASH_FMA) = 0;\r
457     HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_COMT;\r
458 \r
459     //\r
460     // Wait until the write has completed.\r
461     //\r
462     while(HWREG(FLASH_FMC) & FLASH_FMC_COMT)\r
463     {\r
464     }\r
465 \r
466     //\r
467     // Tell the flash controller to write the flash program protection\r
468     // register.\r
469     //\r
470     HWREG(FLASH_FMA) = 1;\r
471     HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_COMT;\r
472 \r
473     //\r
474     // Wait until the write has completed.\r
475     //\r
476     while(HWREG(FLASH_FMC) & FLASH_FMC_COMT)\r
477     {\r
478     }\r
479 \r
480     //\r
481     // Success.\r
482     //\r
483     return(0);\r
484 }\r
485 #endif\r
486 \r
487 //*****************************************************************************\r
488 //\r
489 //! Registers an interrupt handler for the flash interrupt.\r
490 //!\r
491 //! \param pfnHandler is a pointer to the function to be called when the flash\r
492 //! interrupt occurs.\r
493 //!\r
494 //! This sets the handler to be called when the flash interrupt occurs.  The\r
495 //! flash controller can generate an interrupt when an invalid flash access\r
496 //! occurs, such as trying to program or erase a read-only block, or trying to\r
497 //! read from an execute-only block.  It can also generate an interrupt when a\r
498 //! program or erase operation has completed.  The interrupt will be\r
499 //! automatically enabled when the handler is registered.\r
500 //!\r
501 //! \sa IntRegister() for important information about registering interrupt\r
502 //! handlers.\r
503 //!\r
504 //! \return None.\r
505 //\r
506 //*****************************************************************************\r
507 #if defined(GROUP_intregister) || defined(BUILD_ALL) || defined(DOXYGEN)\r
508 void\r
509 FlashIntRegister(void (*pfnHandler)(void))\r
510 {\r
511     //\r
512     // Register the interrupt handler, returning an error if an error occurs.\r
513     //\r
514     IntRegister(INT_FLASH, pfnHandler);\r
515 \r
516     //\r
517     // Enable the flash interrupt.\r
518     //\r
519     IntEnable(INT_FLASH);\r
520 }\r
521 #endif\r
522 \r
523 //*****************************************************************************\r
524 //\r
525 //! Unregisters the interrupt handler for the flash interrupt.\r
526 //!\r
527 //! This function will clear the handler to be called when the flash interrupt\r
528 //! occurs.  This will also mask off the interrupt in the interrupt controller\r
529 //! so that the interrupt handler is no longer called.\r
530 //!\r
531 //! \sa IntRegister() for important information about registering interrupt\r
532 //! handlers.\r
533 //!\r
534 //! \return None.\r
535 //\r
536 //*****************************************************************************\r
537 #if defined(GROUP_intunregister) || defined(BUILD_ALL) || defined(DOXYGEN)\r
538 void\r
539 FlashIntUnregister(void)\r
540 {\r
541     //\r
542     // Disable the interrupt.\r
543     //\r
544     IntDisable(INT_FLASH);\r
545 \r
546     //\r
547     // Unregister the interrupt handler.\r
548     //\r
549     IntUnregister(INT_FLASH);\r
550 }\r
551 #endif\r
552 \r
553 //*****************************************************************************\r
554 //\r
555 //! Enables individual flash controller interrupt sources.\r
556 //!\r
557 //! \param ulIntFlags is a bit mask of the interrupt sources to be enabled.\r
558 //! Can be any of the \b FLASH_FCIM_PROGRAM or \b FLASH_FCIM_ACCESS values.\r
559 //!\r
560 //! Enables the indicated flash controller interrupt sources.  Only the sources\r
561 //! that are enabled can be reflected to the processor interrupt; disabled\r
562 //! sources have no effect on the processor.\r
563 //!\r
564 //! \return None.\r
565 //\r
566 //*****************************************************************************\r
567 #if defined(GROUP_intenable) || defined(BUILD_ALL) || defined(DOXYGEN)\r
568 void\r
569 FlashIntEnable(unsigned long ulIntFlags)\r
570 {\r
571     //\r
572     // Enable the specified interrupts.\r
573     //\r
574     HWREG(FLASH_FCIM) |= ulIntFlags;\r
575 }\r
576 #endif\r
577 \r
578 //*****************************************************************************\r
579 //\r
580 //! Disables individual flash controller interrupt sources.\r
581 //!\r
582 //! \param ulIntFlags is a bit mask of the interrupt sources to be disabled.\r
583 //! Can be any of the \b FLASH_FCIM_PROGRAM or \b FLASH_FCIM_ACCESS values.\r
584 //!\r
585 //! Disables the indicated flash controller interrupt sources.  Only the\r
586 //! sources that are enabled can be reflected to the processor interrupt;\r
587 //! disabled sources have no effect on the processor.\r
588 //!\r
589 //! \return None.\r
590 //\r
591 //*****************************************************************************\r
592 #if defined(GROUP_intdisable) || defined(BUILD_ALL) || defined(DOXYGEN)\r
593 void\r
594 FlashIntDisable(unsigned long ulIntFlags)\r
595 {\r
596     //\r
597     // Disable the specified interrupts.\r
598     //\r
599     HWREG(FLASH_FCIM) &= ~(ulIntFlags);\r
600 }\r
601 #endif\r
602 \r
603 //*****************************************************************************\r
604 //\r
605 //! Gets the current interrupt status.\r
606 //!\r
607 //! \param bMasked is false if the raw interrupt status is required and true if\r
608 //! the masked interrupt status is required.\r
609 //!\r
610 //! This returns the interrupt status for the flash controller.  Either the raw\r
611 //! interrupt status or the status of interrupts that are allowed to reflect to\r
612 //! the processor can be returned.\r
613 //!\r
614 //! \return The current interrupt status, enumerated as a bit field of\r
615 //! \b FLASH_FCMISC_PROGRAM and \b FLASH_FCMISC_ACCESS.\r
616 //\r
617 //*****************************************************************************\r
618 #if defined(GROUP_intgetstatus) || defined(BUILD_ALL) || defined(DOXYGEN)\r
619 unsigned long\r
620 FlashIntGetStatus(tBoolean bMasked)\r
621 {\r
622     //\r
623     // Return either the interrupt status or the raw interrupt status as\r
624     // requested.\r
625     //\r
626     if(bMasked)\r
627     {\r
628         return(HWREG(FLASH_FCMISC));\r
629     }\r
630     else\r
631     {\r
632         return(HWREG(FLASH_FCRIS));\r
633     }\r
634 }\r
635 #endif\r
636 \r
637 //*****************************************************************************\r
638 //\r
639 //! Clears flash controller interrupt sources.\r
640 //!\r
641 //! \param ulIntFlags is the bit mask of the interrupt sources to be cleared.\r
642 //! Can be any of the \b FLASH_FCMISC_PROGRAM or \b FLASH_FCMISC_ACCESS\r
643 //! values.\r
644 //!\r
645 //! The specified flash controller interrupt sources are cleared, so that they\r
646 //! no longer assert.  This must be done in the interrupt handler to keep it\r
647 //! from being called again immediately upon exit.\r
648 //!\r
649 //! \return None.\r
650 //\r
651 //*****************************************************************************\r
652 #if defined(GROUP_intclear) || defined(BUILD_ALL) || defined(DOXYGEN)\r
653 void\r
654 FlashIntClear(unsigned long ulIntFlags)\r
655 {\r
656     //\r
657     // Clear the flash interrupt.\r
658     //\r
659     HWREG(FLASH_FCMISC) = ulIntFlags;\r
660 }\r
661 #endif\r
662 \r
663 //*****************************************************************************\r
664 //\r
665 // Close the Doxygen group.\r
666 //! @}\r
667 //\r
668 //*****************************************************************************\r