1

I'm trying to figure out how suspend/wakeup is implemented for mach-omap2, specifically in Linux 2.6.37 targeted for TI OMAP3530/DM3730.

Here's some related code:

http://lxr.free-electrons.com/source/arch/arm/mach-omap2/pm34xx.c?v=2.6.37

static int omap3_pm_suspend(void)
{
    struct power_state *pwrst;
    int state, ret = 0;

    if (wakeup_timer_seconds || wakeup_timer_milliseconds)
        omap2_pm_wakeup_on_timer(wakeup_timer_seconds,
                     wakeup_timer_milliseconds);

    /* Read current next_pwrsts */
    list_for_each_entry(pwrst, &pwrst_list, node)
        pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm);
    /* Set ones wanted by suspend */
    list_for_each_entry(pwrst, &pwrst_list, node) {
        if (omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state))
            goto restore;
        if (pwrdm_clear_all_prev_pwrst(pwrst->pwrdm))
            goto restore;
    }

    omap_uart_prepare_suspend();
    omap3_intc_suspend();

    omap_sram_idle();

restore:
    /* Restore next_pwrsts */
    list_for_each_entry(pwrst, &pwrst_list, node) {
        state = pwrdm_read_prev_pwrst(pwrst->pwrdm);
        if (state > pwrst->next_state) {
            printk(KERN_INFO "Powerdomain (%s) didn't enter "
                   "target state %d\n",
                   pwrst->pwrdm->name, pwrst->next_state);
            ret = -1;
        }
        omap_set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state);
    }
    if (ret)
        printk(KERN_ERR "Could not enter target state in pm_suspend\n");
    else
        printk(KERN_INFO "Successfully put all powerdomains "
               "to target state\n");

    return ret;
}

I'm really struggling to understand how it works.

It seems like, when the suspend procedure runs to after omap_sram_idle();, system is already in suspend mode and everything just freezes there, in the context of this function. When it wakes up, it simply continues from restore: and restores everything. Is this correct?

Sam Protsenko
  • 14,045
  • 4
  • 59
  • 75
user3528438
  • 2,737
  • 2
  • 23
  • 42

1 Answers1

3

System suspend is happening in the middle of omap_sram_idle(). The second part of omap_sram_idle() is actually restoring code. More precisely, actual sleeping is done by wfi ARM instruction in omap34xx_cpu_suspend() assembler function. Read further for details.

Suspend path

  • take a look at omap_sram_idle() function implementation
  • you can see (judging from comments) that the last line before system falls to sleep is _omap_sram_idle() call (here)
  • _omap_sram_idle() points to omap34xx_cpu_suspend() assembler function, which copied previously to SRAM (so it will not be lost during RAM power-off); take a look at next lines of code:

    1. _omap_sram_idle() assignment (notice that first parameter that is passed to it is omap34xx_cpu_suspend function address)
    2. omap_sram_push() implementation; pay attention to memcpy() call: omap_sram_ceil is start address of SRAM memory, start is address of omap34xx_cpu_suspend() function
    3. omap34xx_cpu_suspend() implementation; it's the actual function being executed at this line (instead of _omap_sram_idle()). Take a look at comment above this function:

      /*
       * Forces OMAP into idle state
       *
       * omap34xx_suspend() - This bit of code just executes the WFI
       * for normal idles.
       *
       * Note: This code get's copied to internal SRAM at boot. When the OMAP
       *       wakes up it continues execution at the point it went to sleep.
       */
      
  • actual sleeping is happening in wfi (Wait For Interrupt) ARM instruction (in omap34xx_cpu_suspend() function); the processor will sleep and it will wake up only when some IRQ will happen

  • there are 2 places in omap34xx_cpu_suspend() when wfi can be executed:
    • if context save is not required: here
    • if context save is required: here

Restore path

Once some wake-up signal happened, CPU will continue to execute instructions just after wfi instruction (that have put it to the sleep in the first place). So your system wakes up in omap34xx_cpu_suspend() assembler function (starting from wait_sdrk_ok: label), then returns back to omap_sram_idle() (to this line), and then returns back to omap3_pm_suspend(), to restore: label.

Sam Protsenko
  • 14,045
  • 4
  • 59
  • 75
  • Great answer! Has this implementation changed over time in later linux versions? – user3528438 May 20 '15 at 22:17
  • 1
    No, not really. You can navigate the same code in kernel 4.0 sources [here](http://lxr.free-electrons.com/source/arch/arm/mach-omap2/pm34xx.c). I don't see any big difference with 2.6. If you will try and see how much commitment was done to this file last time (`git log -- arch/arm/mach-omap2/pm34xx.c`) -- you will see that pm_suspend code was mostly unchanged since 2.6. – Sam Protsenko May 20 '15 at 22:48
  • 1
    Here are some related questions, [iMx35 DDR suspend](http://stackoverflow.com/questions/21779633/i-mx35-suspend-cpu-and-ddr2-from-iram) and [Linux idle processor](http://stackoverflow.com/questions/15058781/how-does-linux-kernel-wake-idle-processor-up-when-new-task-created). Os code needs to put things in a special mode for the `wfi` instructions to enter deeper sleeps. It can be quite complex as DDR can not run code when suspended (and it is often a large current consumer). So, there are omap specifics in this answer, but the concepts apply to all Linux ARM cpus. – artless noise May 21 '15 at 15:33
  • Hi, just wanted to check is it possible to stop suspension of a particularity module/device , like SGX module on Jacinto based Board ? – Milan Aug 02 '17 at 13:41
  • @Milan You mean, suspend the CPU, but keep some IP core running? Maybe it's possible by modifying its driver code, but I don't see how it could be helpful, without CPU running (it's in `wfi` instruction). See [this](https://www.kernel.org/doc/Documentation/power/runtime_pm.txt) for details. What is the problem you are trying to solve? – Sam Protsenko Aug 03 '17 at 12:37
  • @SamProtsenko, Thanks for replying. We have run into as issue where one of our modules doesn't really come out of reset and its state in /sys/power/state is OFF. We have fixed doing this but not sure this is the proper solution to it, https://paste.ubuntu.com/25233104/ . – Milan Aug 03 '17 at 12:40
  • @Milan I think it's better to create a new question, describing your issue with details, steps to reproduce, minimal compilable example, etc. Also it's worth to ask it on TI E2E forum. – Sam Protsenko Aug 03 '17 at 12:45