1 /******************************************************************************
2 *
3 * Copyright (C) 2022-2023 Maxim Integrated Products, Inc. (now owned by
4 * Analog Devices, Inc.),
5 * Copyright (C) 2023-2024 Analog Devices, Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 ******************************************************************************/
20
21 /**
22 * @file mxc_sys.c
23 * @brief System layer driver.
24 * @details This driver is used to control the system layer of the device.
25 */
26
27 /* **** Includes **** */
28 #include <stddef.h>
29 #include <string.h>
30 #include "mxc_device.h"
31 #include "mxc_assert.h"
32 #include "mxc_sys.h"
33 #include "mxc_delay.h"
34 #include "lpgcr_regs.h"
35 #include "gcr_regs.h"
36 #include "fcr_regs.h"
37 #include "mcr_regs.h"
38 #include "pwrseq_regs.h"
39 #include "flc.h"
40 #include "ctb.h"
41
42 /**
43 * @ingroup mxc_sys
44 * @{
45 */
46
47 /* **** Definitions **** */
48 #define MXC_SYS_CLOCK_TIMEOUT MSEC(1)
49
50 // DAP Lock macros
51 #define INFOBLOCK_DAP_LOCK_OFFSET 0x30
52 #define DAP_LOCK_SEQUENCE_01 0x5A5AA5A5
53 #define DAP_LOCK_SEQUENCE_23 0xFFFFFFFF
54
55 /* **** Globals **** */
56
57 /* Symbol defined when loading RISCV image */
58 extern uint32_t _binary_riscv_bin_start;
59
60 /* **** Functions **** */
61
62 /* ************************************************************************** */
MXC_SYS_GetUSN(uint8_t * usn,uint8_t * checksum)63 int MXC_SYS_GetUSN(uint8_t *usn, uint8_t *checksum)
64 {
65 if (usn == NULL) {
66 return E_NULL_PTR;
67 }
68
69 uint32_t *infoblock = (uint32_t *)MXC_INFO0_MEM_BASE;
70
71 /* Read the USN from the info block */
72 MXC_FLC_UnlockInfoBlock(MXC_INFO0_MEM_BASE);
73
74 memset(usn, 0, MXC_SYS_USN_CHECKSUM_LEN);
75
76 usn[0] = (infoblock[0] & 0x007F8000) >> 15;
77 usn[1] = (infoblock[0] & 0x7F800000) >> 23;
78 usn[2] = (infoblock[1] & 0x0000007F) << 1;
79 usn[2] |= (infoblock[0] & 0x80000000) >> 31;
80 usn[3] = (infoblock[1] & 0x00007F80) >> 7;
81 usn[4] = (infoblock[1] & 0x007F8000) >> 15;
82 usn[5] = (infoblock[1] & 0x7F800000) >> 23;
83 usn[6] = (infoblock[2] & 0x007F8000) >> 15;
84 usn[7] = (infoblock[2] & 0x7F800000) >> 23;
85 usn[8] = (infoblock[3] & 0x0000007F) << 1;
86 usn[8] |= (infoblock[2] & 0x80000000) >> 31;
87 usn[9] = (infoblock[3] & 0x00007F80) >> 7;
88 usn[10] = (infoblock[3] & 0x007F8000) >> 15;
89
90 // Compute the checksum
91 if (checksum != NULL) {
92 uint32_t key[4];
93 uint32_t pt32[4];
94 uint32_t check_csum32[4];
95 uint8_t check_csum[MXC_SYS_USN_CHECKSUM_LEN];
96
97 /* Initialize key and plaintext */
98 memset(key, 0, MXC_SYS_USN_CHECKSUM_LEN);
99 memset(pt32, 0, MXC_SYS_USN_CHECKSUM_LEN);
100 memcpy(pt32, usn, MXC_SYS_USN_CHECKSUM_LEN);
101
102 /* Read the checksum from the info block */
103 checksum[1] = ((infoblock[3] & 0x7F800000) >> 23);
104 checksum[0] = ((infoblock[4] & 0x007F8000) >> 15);
105
106 MXC_CTB_Init(MXC_CTB_FEATURE_CIPHER);
107
108 /* Reset the CTB */
109 MXC_CTB->ctrl = MXC_F_CTB_CTRL_RST;
110
111 /* Set the legacy bit */
112 MXC_CTB->ctrl |= MXC_F_CTB_CTRL_FLAG_MODE;
113
114 /* Clear interrupt flags */
115 MXC_CTB->ctrl |= MXC_F_CTB_CTRL_CPH_DONE;
116
117 /* Setup the key source */
118 MXC_CTB->cipher_ctrl = MXC_S_CTB_CIPHER_CTRL_SRC_CIPHERKEY;
119
120 /* Setup the CT calculation */
121 MXC_CTB->cipher_ctrl |= MXC_S_CTB_CIPHER_CTRL_CIPHER_AES128;
122
123 /* Load the key */
124 MXC_CTB->cipher_key[0] = key[0];
125 MXC_CTB->cipher_key[1] = key[1];
126 MXC_CTB->cipher_key[2] = key[2];
127 MXC_CTB->cipher_key[3] = key[3];
128
129 /* Wait for the ready flag */
130 while (!(MXC_CTB->ctrl & MXC_F_CTB_CTRL_RDY)) {}
131
132 /* Copy data to start the operation */
133 MXC_CTB->din[0] = pt32[0];
134 MXC_CTB->din[1] = pt32[1];
135 MXC_CTB->din[2] = pt32[2];
136 MXC_CTB->din[3] = pt32[3];
137
138 /* Wait for and clear the done flag */
139 while (!(MXC_CTB->ctrl & MXC_F_CTB_CTRL_CPH_DONE)) {}
140 MXC_CTB->ctrl |= MXC_F_CTB_CTRL_CPH_DONE;
141
142 /* Copy out the cipher text */
143 check_csum32[0] = MXC_CTB->dout[0];
144 check_csum32[1] = MXC_CTB->dout[1];
145 check_csum32[2] = MXC_CTB->dout[2];
146 check_csum32[3] = MXC_CTB->dout[3];
147
148 memcpy(check_csum, check_csum32, MXC_SYS_USN_CHECKSUM_LEN);
149
150 /* Verify the checksum */
151 if ((checksum[0] != check_csum[0]) || (checksum[1] != check_csum[1])) {
152 MXC_FLC_LockInfoBlock(MXC_INFO0_MEM_BASE);
153 return E_UNKNOWN;
154 }
155 }
156
157 /* Add the info block checksum to the USN */
158 usn[11] = ((infoblock[3] & 0x7F800000) >> 23);
159 usn[12] = ((infoblock[4] & 0x007F8000) >> 15);
160
161 MXC_FLC_LockInfoBlock(MXC_INFO0_MEM_BASE);
162
163 return E_NO_ERROR;
164 }
165
166 /* ************************************************************************** */
MXC_SYS_IsClockEnabled(mxc_sys_periph_clock_t clock)167 int MXC_SYS_IsClockEnabled(mxc_sys_periph_clock_t clock)
168 {
169 /* The mxc_sys_periph_clock_t enum uses enum values that are the offset by 32 and 64 for the perckcn1 register. */
170 if (clock > 63) {
171 clock -= 64;
172 return !(MXC_LPGCR->pclkdis & (0x1 << clock));
173 } else if (clock > 31) {
174 clock -= 32;
175 return !(MXC_GCR->pclkdis1 & (0x1 << clock));
176 } else {
177 return !(MXC_GCR->pclkdis0 & (0x1 << clock));
178 }
179 }
180
181 /* ************************************************************************** */
MXC_SYS_ClockDisable(mxc_sys_periph_clock_t clock)182 void MXC_SYS_ClockDisable(mxc_sys_periph_clock_t clock)
183 {
184 /* The mxc_sys_periph_clock_t enum uses enum values that are the offset by 32 and 64 for the perckcn1 register. */
185 if (clock > 63) {
186 clock -= 64;
187 MXC_LPGCR->pclkdis |= (0x1 << clock);
188 } else if (clock > 31) {
189 clock -= 32;
190 MXC_GCR->pclkdis1 |= (0x1 << clock);
191 } else {
192 MXC_GCR->pclkdis0 |= (0x1 << clock);
193 }
194 }
195
196 /* ************************************************************************** */
MXC_SYS_ClockEnable(mxc_sys_periph_clock_t clock)197 void MXC_SYS_ClockEnable(mxc_sys_periph_clock_t clock)
198 {
199 /* The mxc_sys_periph_clock_t enum uses enum values that are the offset by 32 and 64 for the perckcn1 register. */
200 if (clock > 63) {
201 clock -= 64;
202 MXC_LPGCR->pclkdis &= ~(0x1 << clock);
203 } else if (clock > 31) {
204 clock -= 32;
205 MXC_GCR->pclkdis1 &= ~(0x1 << clock);
206 } else {
207 MXC_GCR->pclkdis0 &= ~(0x1 << clock);
208 }
209 }
210 /* ************************************************************************** */
MXC_SYS_RTCClockEnable()211 void MXC_SYS_RTCClockEnable()
212 {
213 MXC_GCR->clkctrl |= MXC_F_GCR_CLKCTRL_ERTCO_EN;
214 }
215
216 /* ************************************************************************** */
MXC_SYS_RTCClockDisable(void)217 int MXC_SYS_RTCClockDisable(void)
218 {
219 /* Check that the RTC is not the system clock source */
220 if ((MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_SYSCLK_SEL) != MXC_S_GCR_CLKCTRL_SYSCLK_SEL_ERTCO) {
221 MXC_GCR->clkctrl &= ~MXC_F_GCR_CLKCTRL_ERTCO_EN;
222 return E_NO_ERROR;
223 } else {
224 return E_BAD_STATE;
225 }
226 }
227
228 /******************************************************************************/
MXC_SYS_ClockSourceEnable(mxc_sys_system_clock_t clock)229 int MXC_SYS_ClockSourceEnable(mxc_sys_system_clock_t clock)
230 {
231 switch (clock) {
232 case MXC_SYS_CLOCK_IPO:
233 MXC_GCR->clkctrl |= MXC_F_GCR_CLKCTRL_IPO_EN;
234 return MXC_SYS_Clock_Timeout(MXC_F_GCR_CLKCTRL_IPO_RDY);
235 break;
236
237 case MXC_SYS_CLOCK_IBRO:
238 MXC_GCR->clkctrl |= MXC_F_GCR_CLKCTRL_IBRO_EN;
239 return MXC_SYS_Clock_Timeout(MXC_F_GCR_CLKCTRL_IBRO_RDY);
240 break;
241
242 case MXC_SYS_CLOCK_ISO:
243 MXC_GCR->clkctrl |= MXC_F_GCR_CLKCTRL_ISO_EN;
244 return MXC_SYS_Clock_Timeout(MXC_F_GCR_CLKCTRL_ISO_RDY);
245 break;
246
247 case MXC_SYS_CLOCK_EXTCLK:
248 return MXC_GPIO_Config(&gpio_cfg_extclk);
249 break;
250
251 case MXC_SYS_CLOCK_INRO:
252 // The 80k clock is always enabled
253 return MXC_SYS_Clock_Timeout(MXC_F_GCR_CLKCTRL_INRO_RDY);
254 break;
255
256 case MXC_SYS_CLOCK_ERFO:
257 MXC_GCR->btleldoctrl |= MXC_F_GCR_BTLELDOCTRL_LDOTXEN | MXC_F_GCR_BTLELDOCTRL_LDORXEN;
258
259 MXC_GCR->clkctrl |= MXC_F_GCR_CLKCTRL_ERFO_EN;
260 return MXC_SYS_Clock_Timeout(MXC_F_GCR_CLKCTRL_ERFO_RDY);
261 break;
262
263 case MXC_SYS_CLOCK_ERTCO:
264 MXC_GCR->clkctrl |= MXC_F_GCR_CLKCTRL_ERTCO_EN;
265 return MXC_SYS_Clock_Timeout(MXC_F_GCR_CLKCTRL_ERTCO_RDY);
266 break;
267
268 default:
269 return E_BAD_PARAM;
270 break;
271 }
272 }
273
274 /******************************************************************************/
MXC_SYS_ClockSourceDisable(mxc_sys_system_clock_t clock)275 int MXC_SYS_ClockSourceDisable(mxc_sys_system_clock_t clock)
276 {
277 uint32_t current_clock;
278
279 current_clock = MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_SYSCLK_SEL;
280
281 // Don't turn off the clock we're running on
282 if (clock == current_clock) {
283 return E_BAD_PARAM;
284 }
285
286 switch (clock) {
287 case MXC_SYS_CLOCK_IPO:
288 MXC_GCR->clkctrl &= ~MXC_F_GCR_CLKCTRL_IPO_EN;
289 break;
290
291 case MXC_SYS_CLOCK_ISO:
292 MXC_GCR->clkctrl &= ~MXC_F_GCR_CLKCTRL_ISO_EN;
293 break;
294
295 case MXC_SYS_CLOCK_IBRO:
296 if ((MXC_GCR->pm & MXC_F_GCR_PM_MODE) == MXC_S_GCR_PM_MODE_UPM) {
297 MXC_GCR->clkctrl &= ~MXC_F_GCR_CLKCTRL_IBRO_EN;
298 }
299 break;
300
301 case MXC_SYS_CLOCK_EXTCLK:
302 // MXC_GCR->clkctrl &= ~MXC_F_GCR_CLKCTRL_EXTCLK_EN;
303 break;
304
305 case MXC_SYS_CLOCK_INRO:
306 // The 80k clock is always enabled
307 break;
308
309 case MXC_SYS_CLOCK_ERFO:
310 MXC_GCR->clkctrl &= ~MXC_F_GCR_CLKCTRL_ERFO_EN;
311 break;
312
313 case MXC_SYS_CLOCK_ERTCO:
314 MXC_GCR->clkctrl &= ~MXC_F_GCR_CLKCTRL_ERTCO_EN;
315 break;
316
317 default:
318 return E_BAD_PARAM;
319 }
320
321 return E_NO_ERROR;
322 }
323
324 /* ************************************************************************** */
MXC_SYS_Clock_Timeout(uint32_t ready)325 int MXC_SYS_Clock_Timeout(uint32_t ready)
326 {
327 // Start timeout, wait for ready
328 MXC_DelayAsync(MXC_SYS_CLOCK_TIMEOUT, NULL);
329 #ifndef ME18_WLP_TEST
330 /* TODO: Timeout on clock switch, use this for untrimmed parts. */
331 while (!(MXC_GCR->clkctrl & ready)) {}
332 return E_NO_ERROR;
333 #else
334 return E_NO_ERROR;
335 #endif
336 }
337
338 /* ************************************************************************** */
MXC_SYS_Clock_Select(mxc_sys_system_clock_t clock)339 int MXC_SYS_Clock_Select(mxc_sys_system_clock_t clock)
340 {
341 uint32_t current_clock;
342
343 // Save the current system clock
344 current_clock = MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_SYSCLK_SEL;
345
346 switch (clock) {
347 case MXC_SYS_CLOCK_IPO:
348
349 // Enable IPO clock
350 if (!(MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_IPO_EN)) {
351 MXC_SYS_ClockSourceEnable(MXC_SYS_CLOCK_IPO);
352 }
353
354 // Set IPO clock as System Clock
355 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL,
356 MXC_S_GCR_CLKCTRL_SYSCLK_SEL_IPO);
357
358 break;
359
360 case MXC_SYS_CLOCK_ISO:
361
362 // Enable ISO clock
363 if (!(MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_ISO_EN)) {
364 MXC_SYS_ClockSourceEnable(MXC_SYS_CLOCK_ISO);
365 }
366
367 // Set ISO clock as System Clock
368 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL,
369 MXC_S_GCR_CLKCTRL_SYSCLK_SEL_ISO);
370
371 break;
372
373 case MXC_SYS_CLOCK_IBRO:
374
375 // Enable IBRO clock
376 if (!(MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_IBRO_EN)) {
377 MXC_SYS_ClockSourceEnable(MXC_SYS_CLOCK_IBRO);
378 }
379
380 // Set IBRO clock as System Clock
381 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL,
382 MXC_S_GCR_CLKCTRL_SYSCLK_SEL_IBRO);
383
384 break;
385
386 case MXC_SYS_CLOCK_EXTCLK:
387 MXC_SYS_ClockSourceEnable(MXC_SYS_CLOCK_EXTCLK);
388
389 // Set external clock clock as System Clock
390 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL,
391 MXC_S_GCR_CLKCTRL_SYSCLK_SEL_EXTCLK);
392
393 break;
394
395 case MXC_SYS_CLOCK_ERFO:
396
397 // Enable ERFO clock
398 if (!(MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_ERFO_EN)) {
399 MXC_SYS_ClockSourceEnable(MXC_SYS_CLOCK_ERFO);
400 }
401
402 // Set ERFO clock as System Clock
403 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL,
404 MXC_S_GCR_CLKCTRL_SYSCLK_SEL_ERFO);
405
406 break;
407
408 case MXC_SYS_CLOCK_INRO:
409 // Set INRO clock as System Clock
410 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL,
411 MXC_S_GCR_CLKCTRL_SYSCLK_SEL_INRO);
412
413 break;
414
415 case MXC_SYS_CLOCK_ERTCO:
416
417 // Enable ERTCO clock
418 if (!(MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_ERTCO_EN)) {
419 MXC_SYS_ClockSourceEnable(MXC_SYS_CLOCK_ERTCO);
420 }
421
422 // Set ERTCO clock as System Clock
423 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL,
424 MXC_S_GCR_CLKCTRL_SYSCLK_SEL_ERTCO);
425
426 break;
427
428 default:
429 return E_BAD_PARAM;
430 }
431
432 // Wait for system clock to be ready
433 if (MXC_SYS_Clock_Timeout(MXC_F_GCR_CLKCTRL_SYSCLK_RDY) != E_NO_ERROR) {
434 // Restore the old system clock if timeout
435 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_SEL, current_clock);
436
437 return E_TIME_OUT;
438 }
439
440 // Update the system core clock
441 SystemCoreClockUpdate();
442
443 return E_NO_ERROR;
444 }
445
446 /* ************************************************************************** */
MXC_SYS_SetClockDiv(mxc_sys_system_clock_div_t div)447 void MXC_SYS_SetClockDiv(mxc_sys_system_clock_div_t div)
448 {
449 /* Return if this setting is already current */
450 if (div == MXC_SYS_GetClockDiv()) {
451 return;
452 }
453
454 MXC_SETFIELD(MXC_GCR->clkctrl, MXC_F_GCR_CLKCTRL_SYSCLK_DIV, div);
455
456 SystemCoreClockUpdate();
457 }
458
459 /* ************************************************************************** */
MXC_SYS_GetClockDiv(void)460 mxc_sys_system_clock_div_t MXC_SYS_GetClockDiv(void)
461 {
462 return (MXC_GCR->clkctrl & MXC_F_GCR_CLKCTRL_SYSCLK_DIV);
463 }
464
465 /* ************************************************************************** */
MXC_SYS_Reset_Periph(mxc_sys_reset_t reset)466 void MXC_SYS_Reset_Periph(mxc_sys_reset_t reset)
467 {
468 /* The mxc_sys_reset_t enum uses enum values that are the offset by 32 and 64 for the rst register. */
469 if (reset > 63) {
470 reset -= 64;
471 MXC_LPGCR->rst = (0x1 << reset);
472 while (MXC_LPGCR->rst & (0x1 << reset)) {}
473 } else if (reset > 31) {
474 reset -= 32;
475 MXC_GCR->rst1 = (0x1 << reset);
476 while (MXC_GCR->rst1 & (0x1 << reset)) {}
477 } else {
478 MXC_GCR->rst0 = (0x1 << reset);
479 while (MXC_GCR->rst0 & (0x1 << reset)) {}
480 }
481 }
482
483 /* ************************************************************************** */
MXC_SYS_RISCVRun(void)484 void MXC_SYS_RISCVRun(void)
485 {
486 /* Disable the the RSCV */
487 MXC_GCR->pclkdis1 |= MXC_F_GCR_PCLKDIS1_CPU1;
488
489 /* Set the interrupt vector base address */
490 MXC_FCR->urvbootaddr = (uint32_t)&_binary_riscv_bin_start;
491
492 /* Power up the RSCV */
493 MXC_GCR->pclkdis1 &= ~(MXC_F_GCR_PCLKDIS1_CPU1);
494
495 /* CPU1 reset */
496 MXC_GCR->rst1 |= MXC_F_GCR_RST1_CPU1;
497 }
498
499 /* ************************************************************************** */
MXC_SYS_RISCVShutdown(void)500 void MXC_SYS_RISCVShutdown(void)
501 {
502 /* Disable the the RSCV */
503 MXC_GCR->pclkdis1 |= MXC_F_GCR_PCLKDIS1_CPU1;
504 }
505
506 /* ************************************************************************** */
MXC_SYS_RiscVClockRate(void)507 uint32_t MXC_SYS_RiscVClockRate(void)
508 {
509 // If in LPM mode and the PCLK is selected as the RV32 clock source,
510 if (((MXC_GCR->pm & MXC_F_GCR_PM_MODE) == MXC_S_GCR_PM_MODE_LPM) &&
511 (MXC_PWRSEQ->lpcn & MXC_F_PWRSEQ_LPCN_ISOCLK_SELECT)) {
512 return ISO_FREQ;
513 } else {
514 return PeripheralClock;
515 }
516 }
517
518 /* ************************************************************************** */
MXC_SYS_LockDAP_Permanent(void)519 int MXC_SYS_LockDAP_Permanent(void)
520 {
521 #ifdef DEBUG
522 // Locking the DAP is not supported while in DEBUG.
523 // To use this function, build for release ("make release")
524 // or set DEBUG = 0
525 // (see https://analogdevicesinc.github.io/msdk/USERGUIDE/#build-tables)
526 return E_NOT_SUPPORTED;
527 #else
528 int err;
529 uint32_t info_blk_addr;
530 uint32_t lock_sequence[4];
531
532 // Infoblock address to write lock sequence to
533 info_blk_addr = MXC_INFO_MEM_BASE + INFOBLOCK_DAP_LOCK_OFFSET;
534
535 // Set lock sequence
536 lock_sequence[0] = DAP_LOCK_SEQUENCE_01;
537 lock_sequence[1] = DAP_LOCK_SEQUENCE_01;
538 lock_sequence[2] = DAP_LOCK_SEQUENCE_23;
539 lock_sequence[3] = DAP_LOCK_SEQUENCE_23;
540
541 // Initialize FLC
542 MXC_FLC_Init();
543
544 // Unlock infoblock
545 MXC_FLC_UnlockInfoBlock(info_blk_addr);
546
547 // Write DAP lock sequence to infoblock
548 err = MXC_FLC_Write128(info_blk_addr, lock_sequence);
549
550 // Re-lock infoblock
551 MXC_FLC_LockInfoBlock(info_blk_addr);
552
553 return err;
554 #endif
555 }
556
557 /**@} end of mxc_sys */
558