summaryrefslogtreecommitdiff
path: root/examples/imx7_colibri_m4/low_power_demo/lpm_mcore.c
blob: 96857b858156617f1d3226444b7fe6af6a9c6f7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
/*
 * Copyright (c) 2015-2016, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <string.h>
#include "board.h"
#include "lpm_mcore.h"
#include "debug_console_imx.h"
#include "ccm_imx7d.h"
#include "mu_imx.h"
#include "FreeRTOS.h"
#include "task.h"
#include "gpt.h"

unsigned long configCPU_CLOCK_HZ = configCPU_CLOCK_HZ_DEFAULT;
extern void vPortSetupTimerInterrupt(void);

#define MAXIMUM_24M_DIV 15

static LPM_POWER_STATUS_M4 m4_lpm_state = LPM_M4_STATE_RUN;

static P_WAKEUP_INT_ELE g_wakeup_int_list;

/*
 * Send Message to A7
 */
static void LPM_MCORE_SendMessage(uint32_t msg)
{
    while (0 == (MUB_SR & MU_SR_TEn(0x8 >> LPM_MCORE_MU_CHANNEL)));
    MUB->TR[LPM_MCORE_MU_CHANNEL] = msg;
    while ((MUB_SR & MU_SR_EP_MASK) != 0);
}

#if (defined(LPM_MCORE_PRINT_DEBUG_INFO) && (LPM_MCORE_PRINT_DEBUG_INFO))
/*
 * Provide a spinning delay, the actual delay time is dependent on the CPU freq
 */
static void my_delay(void)
{
    uint32_t i, j, k;
    for (i=0; i!=DELAY_CNT; i++)
        for (j=0; j!=DELAY_CNT; j++)
            for (k=0; k!=DELAY_CNT; k++)
                __NOP();
    return;
}

/*
 * Use the delay function to demostrate current CPU running freq
 */
static void verify_clock_speed(void)
{
    uint32_t i;
    for (i=0; i!=DELAY_LOOP_CNT_LOW_SPEED; i++) {
        my_delay();
        PRINTF("\rVerify M4 Speed : %2d of %d ... ", i+1, DELAY_LOOP_CNT_LOW_SPEED);
    }
    PRINTF("Done.\r\n");
}
#endif

/*
 * initialize the wakeup interrupt list
 */
static void lpm_init_wakeup_interrupt_list() {
    g_wakeup_int_list = NULL;
}

/*
 * add a new irq to wakeup interrupt link list
 */
static void lpm_add_wakeup_interrupt_list(uint32_t irq_no)
{
    P_WAKEUP_INT_ELE cur_ele = g_wakeup_int_list;
    P_WAKEUP_INT_ELE p;

    if (cur_ele == NULL) {
        /*
         * first element to add
         */
        p = pvPortMalloc(sizeof(WAKEUP_INT_ELE));
        p->irq_no = irq_no;
        p->next = NULL;
        g_wakeup_int_list = p;
    } else {
        for (;;) {
            if (cur_ele->irq_no == irq_no) {
                /*
                 * already in the link list
                 *      - return directly
                 */
                break;
            }
            else if (cur_ele->next == NULL) {
                /*
                 * can't find the element
                 *      - insert into the end
                 */
                p = pvPortMalloc(sizeof(WAKEUP_INT_ELE));
                p->irq_no = irq_no;
                p->next = NULL;
                cur_ele->next = p;
            } else {
                cur_ele = cur_ele->next;
            }
        }
    }
}

/*
 * remove an exsiting irq to wakeup interrupt link list
 */
static void lpm_del_wakeup_interrupt_list(uint32_t irq_no)
{
    P_WAKEUP_INT_ELE cur_ele = g_wakeup_int_list;
    P_WAKEUP_INT_ELE p;

    if (cur_ele != NULL) {
        if (cur_ele->irq_no == irq_no) {
            /*first element is the target*/
            p = g_wakeup_int_list;
            g_wakeup_int_list = p->next;
            vPortFree(p);
        } else {
            for (;;) {
                p = cur_ele->next;
                if (p == NULL) {
                    /*
                     * can't find the element
                     *      - return directly
                     */
                    break;
                } else {
                    if (p->irq_no == irq_no) {
                        /*
                         * Find the target "p"
                         */
                        cur_ele->next = p->next;
                        vPortFree(p);
                        break;
                    } else {
                        cur_ele = cur_ele->next;
                    }
                }
            }
        }
    }
}


/*
 * register a IRQ source as M4 wakeup source
 */
void LPM_MCORE_RegisterWakeupInterrupt(GPC_Type * base, uint32_t irq_no, GPC_IRQ_WAKEUP_MODE wakeup_mode)
{
    /*register wakeup interrupt for M4 in GPC*/
    GPC_EnableM4WakeupIRQ(base, irq_no, wakeup_mode);

    if (wakeup_mode == GPC_IRQ_WAKEUP_ENABLE) {
        /*add an element to link list*/
        lpm_add_wakeup_interrupt_list(irq_no);
    } else {
        /*delete an element to link list*/
        lpm_del_wakeup_interrupt_list(irq_no);
    }
}


/*
 * Low Power Management initialization
 */
void LPM_MCORE_Init(GPC_Type * base)
{
    // Init GPC
    GPC_Init(base);

    // Init the wakeup interrupt link list
    lpm_init_wakeup_interrupt_list();
}


/*
 * get the current m4 LPM state
 */
LPM_POWER_STATUS_M4 LPM_MCORE_GetPowerStatus(GPC_Type * base)
{
    return m4_lpm_state;
} 

/*
 * on-the-fly change m4 parent clock between 24MHz and 240MHz
 */
void LPM_MCORE_ChangeM4Clock(LPM_M4_CLOCK_SPEED target)
{
    // change CCM Root to change M4 clock
    switch (target) {
        case LPM_M4_LOW_FREQ:
            if (CCM_GetRootMux(CCM, ccmRootM4) != ccmRootmuxM4Osc24m) {
            #if (defined(LPM_MCORE_PRINT_DEBUG_INFO) && (LPM_MCORE_PRINT_DEBUG_INFO))
                PRINTF("Change M4 clock freq to 24M\r\n");
            #endif
                CCM_SetRootMux(CCM, ccmRootM4, ccmRootmuxM4Osc24m);
            }
            configCPU_CLOCK_HZ = 24000000ul;
            break;
        case LPM_M4_HIGH_FREQ:
            if (CCM_GetRootMux(CCM, ccmRootM4) != ccmRootmuxM4SysPllDiv2) {
            #if (defined(LPM_MCORE_PRINT_DEBUG_INFO) && (LPM_MCORE_PRINT_DEBUG_INFO))
                PRINTF("Change M4 clock freq to SysPLL Div2 (240M)\r\n");
            #endif
                CCM_SetRootMux(CCM, ccmRootM4, ccmRootmuxM4SysPllDiv2);
            }
            configCPU_CLOCK_HZ = 240000000ul;
            break;
        default:
            break;
    }
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
        vPortSetupTimerInterrupt();
#if (defined(LPM_MCORE_PRINT_DEBUG_INFO) && (LPM_MCORE_PRINT_DEBUG_INFO))
    verify_clock_speed();
#endif
}

/*
 * cycle M4 low power mode to next state, the state machine is
 *
 *        +---> "RUN" ---> "WAIT" ---> "STOP" ---+
 *        |                                      |
 *        +--------------------------------------+
 */
void LPM_MCORE_SetPowerStatus(GPC_Type * base, LPM_POWER_STATUS_M4 m4_next_lpm)
{
    uint32_t next_lpm = GPC_LPCR_M4_LPM0(0);
    switch (m4_next_lpm) {
        case LPM_M4_STATE_RUN:
            next_lpm = GPC_LPCR_M4_LPM0(0);
            break;
        case LPM_M4_STATE_WAIT:
            next_lpm = GPC_LPCR_M4_LPM0(1);
            break;
        case LPM_M4_STATE_STOP:
            next_lpm = GPC_LPCR_M4_LPM0(2);
            break;
        default:
            break;
    }

    /*
     * Patch, let GPC-M4 observe the GPR0 interrupt for a period as long
     * as 5 32KHz clock cycle before set it to a Low power status
     */
    if (m4_next_lpm != LPM_M4_STATE_RUN)
    {
        uint32_t i;
        LPM_MCORE_RegisterWakeupInterrupt(GPC, GPT4_IRQn, GPC_IRQ_WAKEUP_ENABLE);
        for (i=0; i!=GPC_SYNC_DELAY_CNT; i++)
            __NOP();
        LPM_MCORE_RegisterWakeupInterrupt(GPC, GPT4_IRQn, GPC_IRQ_WAKEUP_DISABLE);
    }

    GPC_SetM4NextLPM(base, next_lpm);

    /*change lpm state variable*/
    m4_lpm_state = m4_next_lpm;
}



/*
 * Give readable string of current M4 lpm state
 */
const char* LPM_MCORE_GetPowerStatusString(void)
{
    switch (m4_lpm_state) {
        case LPM_M4_STATE_RUN:
            return "RUN";
        case LPM_M4_STATE_WAIT:
            return "WAIT";
        case LPM_M4_STATE_STOP:
            return "STOP";
        default:
            return "UNKNOWN";
    }
}

/*
 * Check if A7 LPM Driver is ready, an "Once Ready, Always Ready" logic is used
 */
uint32_t LPM_MCORE_CheckPeerReady(void) 
{
    static uint32_t a7_ready = 0;
    if (!a7_ready) {
        a7_ready = MU_GetFlags(MUB) & MU_SR_Fn(1);
    }
    return a7_ready;
}

/*
 * Use MU Flag to indicate to A7 that low power management in M4 is ready
 */
void LPM_MCORE_SetSelfReady(void)
{
    MU_SetFlags(MUB, MU_CR_Fn(1));
}

/*
 * This function modify BASEPRI to configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY and
 * wakeup interrupt's NVIC->Priority to configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY - 1
 * The effect is all non-wakeup interrupt gets mute
 * The original basepri settings are stored into pBasepriBackup
 * The original wakeup interrupt nvic->priority settings are stored into linklist
 */
void lpm_disable_non_wakeup_interrupt(uint32_t* pBasepriBackup)
{
    P_WAKEUP_INT_ELE ele;
    uint32_t irq_no;
#if defined(__CC_ARM)
    register uint32_t __regBasePri __ASM("basepri");
    *pBasepriBackup = __regBasePri;
    __regBasePri  = (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - __NVIC_PRIO_BITS));
#else
    *pBasepriBackup = __get_BASEPRI();
    __set_BASEPRI(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - __NVIC_PRIO_BITS));
#endif
    /*
     * Make exceptions to wakeup interrupts, they are stored in "g_wakeup_int_list"
     */
    ele = g_wakeup_int_list;
    for (;;) {
        if (ele == NULL)
            break;

        /* 
         * Store the current Priority into ele backup field
         * Change the Priority to "configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY - 1"
         */
        irq_no = ele->irq_no;
        ele->irq_priority_backup = NVIC->IP[irq_no];
        NVIC->IP[irq_no] = (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY - 1) << (8 - __NVIC_PRIO_BITS);

        /*
         * Move to next
         */
        ele = ele->next;
    }
    __DSB();
    __ISB();
}

/*
 * This function restores BASEPRI and wakeup interrupt nvic priority settings
 * It recover interrupt settings made by lpm_disable_non_wakeup_interrupt
 */
void lpm_enable_non_wakeup_interrupt(uint32_t basePriBackup)
{
    P_WAKEUP_INT_ELE ele;
    uint32_t irq_no;
#if defined(__CC_ARM)
    register uint32_t __regBasePri __ASM("basepri");
#endif
    /*
     * first restore wakeup interrupt priority
     */
    ele = g_wakeup_int_list;
    for (;;) {
        if (ele == NULL)
            break;

        /* 
         * Restore the original priority
         */
        irq_no = ele->irq_no;
        NVIC->IP[irq_no] = ele->irq_priority_backup;

        /*
         * Move to next
         */
        ele = ele->next;
    }
#if defined(__CC_ARM)
    __regBasePri = basePriBackup & 0xFF;
#else
    __set_BASEPRI(basePriBackup);
#endif
    // infinite_loop();
    // Are these necessary?
    __DSB();
    __ISB();
}


/*
 * The sleep function inserted into FreeRTOS idle task hook
 */
void LPM_MCORE_WaitForInt(void)
{
    uint32_t priMaskBackup;

    /* 
     * Only when 
     *      1. A7 peer is ready
     *      2. safe sleep function has been put into TCM
     *      3. m4 true sleep mode has been allowed
     *      4. m4 current lpm mode is wait / stop
     *  The "power save wfi" routine will be executed
     *  Otherwise "normal wfi" will be executed
     *
     *  In Power Save WFI
     *      - PRIMASK is set, so all interrupt handler won't be executed
     *      - BASEPRI and NVIC->Priority is modified so that only wakeup interrupt can
     *        wake up M4 from WFI
     *      - After M4 wake up, NVIC->Priority, BASEPRI and PRIMASK are restored so that
     *        the system return to normal mode
     *
     *  There is a critical section code which is in "runInRAM", inside it M4 will
     *  inform A7 it release the high bus. A7 can then shutdown the high bus which
     *  will make all highbus related peripherals losing functionality, including
     *  DDR, so code in "runInRAM" should run in TCM and don't have any access to
     *  other part of memory
     */
    if (LPM_MCORE_CheckPeerReady() && false)
    {
        volatile uint32_t* reg_lpcr_m4 = &GPC->LPCR_M4;

        uint32_t next_lpm_mode = *reg_lpcr_m4 & GPC_LPCR_M4_LPM0_MASK;
        uint32_t basePriBackup;

        /* Save current PRIMASK value. */
        priMaskBackup = __get_PRIMASK();

        /* 
         * Set PRIMASK to avoid execution of any enabled ISR. 
         *      Note : PRIMASK will not prevent interrupt to wake up M4 from WFI
         *             but it will prevent interrupt handler from running
         */
        __set_PRIMASK(1);
        /*
         * Some of the code should be moved out of "runInRAM"
         */
        switch (next_lpm_mode) {
            case LPCR_M4_RUN:
                /*
                 * STOP -> RUN
                 */
                /*
                 * tell A7 the next LPM mode is RUN
                 */
                /*
                 * the WFI will be wakeup by any enabled interrupt
                 */
                __WFI();
                break;
            case LPCR_M4_WAIT:
            case LPCR_M4_STOP:
                /*
                 * RUN -> WAIT or WAIT -> STOP
                 */
                /*
                 * tell A7 the next LPM mode is WAIT/STOP
                 */
                if (next_lpm_mode == LPCR_M4_WAIT)
                    LPM_MCORE_SendMessage(MSG_LPM_M4_WAIT);
                else if (next_lpm_mode == LPCR_M4_STOP)
                    LPM_MCORE_SendMessage(MSG_LPM_M4_STOP);
                /* 
                 * do modification to BASEPRI and NVIC->Priority settings so that
                 * all interrupt except wakeup interrupt are disabled
                 */
                lpm_disable_non_wakeup_interrupt(&basePriBackup);

                /*
                 * Inside "runInRAM", M4 will inform A7 that it release the highbus. Later
                 * when M4 is waken up, it will request A7 to resume highbus. This section
                 * of code must run in TCM to avoid accessing highbus dependent resouces
                 */
                __WFI();

                // Restore Basepri and NVIC->Priority settings
                lpm_enable_non_wakeup_interrupt(basePriBackup);
                break;
            default:
                break;
        }
        /* 
         * Recover PRIMASK register value. this will enable the wakeup interrupt
         * handler and will activate the main task immediately
         */
        __set_PRIMASK(priMaskBackup);
    }
    else {
        /*
         * Normal WFI which will be wakeup by any enabled interrupt
         */
        __WFI();
    }
}