Confirmed Vendor-dependent power saving implementation method of Linux Kernel based on i.mx6.

at first

https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.52.tar.gz

Based on the code of mach-imx / pm-imx6, I will summarize what to do when implementing the power saving mode.

TL;DR --I briefly checked the power saving implementation method based on i.mx6 of Linux Kernel 4.1. --Check the implementation of WFI by executing sleep to mem and putting the memory in the self-refresh state. --Use the function provided by Linux as the function to copy the prepared function to the dedicated SRAM.

arch/arm/mach-imx/pm-imx6.c


static const struct platform_suspend_ops imx6q_pm_ops = {
    .enter = imx6q_pm_enter,
    .valid = imx6q_pm_valid,
};

imx6q_pm_valid

It supports STANDBY and MEM.

arch/arm/mach-imx/pm-imx6.c


static int imx6q_pm_valid(suspend_state_t state)
{
        return (state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM);
}

imx6q_pm_enter

The contents of enter are also very simple and easy to understand!

arch/arm/mach-imx/pm-imx6.c


static int imx6q_pm_enter(suspend_state_t state)
{
        switch (state) {
        case PM_SUSPEND_STANDBY:
                imx6q_set_lpm(STOP_POWER_ON);
                imx6q_set_int_mem_clk_lpm(true);
                imx_gpc_pre_suspend(false);
                if (cpu_is_imx6sl())
                        imx6sl_set_wait_clk(true);
                /* Zzz ... */
                cpu_do_idle();
                if (cpu_is_imx6sl())
                        imx6sl_set_wait_clk(false);
                imx_gpc_post_resume();
                imx6q_set_lpm(WAIT_CLOCKED);
                break;
        case PM_SUSPEND_MEM:
                imx6q_set_lpm(STOP_POWER_OFF);
                imx6q_set_int_mem_clk_lpm(false);
                imx6q_enable_wb(true);
                /*
                 * For suspend into ocram, asm code already take care of
                 * RBC setting, so we do NOT need to do that here.
                 */
                if (!imx6_suspend_in_ocram_fn)
                        imx6_enable_rbc(true);
                imx_gpc_pre_suspend(true);
                imx_anatop_pre_suspend();
                /* Zzz ... */
                cpu_suspend(0, imx6q_suspend_finish);
                if (cpu_is_imx6q() || cpu_is_imx6dl())
                        imx_smp_prepare();
                imx_anatop_post_resume();
                imx_gpc_post_resume();
                imx6_enable_rbc(false);
                imx6q_enable_wb(false);
                imx6q_set_int_mem_clk_lpm(true);
                imx6q_set_lpm(WAIT_CLOCKED);
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

cpu_do_idle() cpu_do_idle (); is a simple implementation that just calls wfi.

arch/arm64/mm/proc.S


/*
 *      cpu_do_idle()
 *
 *      Idle the processor (wait for interrupt).
 */
ENTRY(cpu_do_idle)
        dsb     sy                              // WFI may enter a low-power mode
        wfi
        ret
ENDPROC(cpu_do_idle)

cpu_suspend()

On the other hand, cpu_suspend () seems to execute the function given as an argument.

arch/arm/kernel/suspend.c


#ifdef CONFIG_MMU
int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
{
        struct mm_struct *mm = current->active_mm;
        u32 __mpidr = cpu_logical_map(smp_processor_id());
        int ret;

        if (!idmap_pgd)
                return -EINVAL;

        /*
         * Provide a temporary page table with an identity mapping for
         * the MMU-enable code, required for resuming.  On successful
         * resume (indicated by a zero return code), we need to switch
         * back to the correct page tables.
         */
        ret = __cpu_suspend(arg, fn, __mpidr);
        if (ret == 0) {
                cpu_switch_mm(mm->pgd, mm);
                local_flush_bp_all();
                local_flush_tlb_all();
        }

        return ret;
}
#else
int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
{
        u32 __mpidr = cpu_logical_map(smp_processor_id());
        return __cpu_suspend(arg, fn, __mpidr);
}
#define idmap_pgd       NULL
#endif

imx6q_suspend_finish()

The argument at this time is ʻimx6q_suspend_finish () `.

arch/arm/mach-imx/pm-imx6.c


static int imx6q_suspend_finish(unsigned long val)
{
        if (!imx6_suspend_in_ocram_fn) {
                cpu_do_idle();
        } else {
                /*
                 * call low level suspend function in ocram,
                 * as we need to float DDR IO.
                 */
                local_flush_tlb_all();
                imx6_suspend_in_ocram_fn(suspend_ocram_base);
        }

        return 0;
}

imx6_suspend_in_orcam_fn()

The ʻimx6_suspend_in_orcam_fn ()` called from here is defined as a function pointer.

arch/arm/mach-imx/pm-imx6.c


static void __iomem *ccm_base;
static void __iomem *suspend_ocram_base;
static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase);

The imx6_suspend function is copied on ocram at the time of initialization at startup.

As you can see from the code, ocram is mmi-sram, that is, built-in SRAM. The data stored here can basically be referenced even when DDR is in a memory refresh state. There is a sleep implementation here.

arch/arm/mach-imx/pm-imx6.c


static int __init imx6q_suspend_init(const struct imx6_pm_socdata *socdata)
{
        node = of_find_compatible_node(NULL, NULL, "mmio-sram");
        if (!node) {
                pr_warn("%s: failed to find ocram node!\n", __func__);
                return -ENODEV;
        }

        pdev = of_find_device_by_node(node);
        if (!pdev) {
                pr_warn("%s: failed to find ocram device!\n", __func__);
                ret = -ENODEV;
                goto put_node;
        }

        ocram_pool = dev_get_gen_pool(&pdev->dev);
        if (!ocram_pool) {
                pr_warn("%s: ocram pool unavailable!\n", __func__);
                ret = -ENODEV;
                goto put_node;
        }

        ocram_base = gen_pool_alloc(ocram_pool, MX6Q_SUSPEND_OCRAM_SIZE);
        if (!ocram_base) {
                pr_warn("%s: unable to alloc ocram!\n", __func__);
                ret = -ENOMEM;
                goto put_node;
        }

        ocram_pbase = gen_pool_virt_to_phys(ocram_pool, ocram_base);

        suspend_ocram_base = __arm_ioremap_exec(ocram_pbase,
                MX6Q_SUSPEND_OCRAM_SIZE, false);

<Omitted>
        imx6_suspend_in_ocram_fn = fncpy(
                suspend_ocram_base + sizeof(*pm_info),
                &imx6_suspend,
                MX6Q_SUSPEND_OCRAM_SIZE - sizeof(*pm_info));

fncpy()

Fncpy (), which copies a function to another memory address, is defined as a function that can be used for general purposes.

arch/arm/include/asm/fncpy.h


/*
 * Minimum alignment requirement for the source and destination addresses
 * for function copying.
 */
#define FNCPY_ALIGN 8

#define fncpy(dest_buf, funcp, size) ({                                 \
        uintptr_t __funcp_address;                                      \
        typeof(funcp) __result;                                         \
                                                                        \
        asm("" : "=r" (__funcp_address) : "0" (funcp));                 \
                                                                        \
        /*                                                              \
         * Ensure alignment of source and destination addresses,        \
         * disregarding the function's Thumb bit:                       \
         */                                                             \
        BUG_ON((uintptr_t)(dest_buf) & (FNCPY_ALIGN - 1) ||             \
                (__funcp_address & ~(uintptr_t)1 & (FNCPY_ALIGN - 1))); \
                                                                        \
        memcpy(dest_buf, (void const *)(__funcp_address & ~1), size);   \
        flush_icache_range((unsigned long)(dest_buf),                   \
                (unsigned long)(dest_buf) + (size));                    \
                                                                        \
        asm("" : "=r" (__result)                                        \
                : "0" ((uintptr_t)(dest_buf) | (__funcp_address & 1))); \
                                                                        \
        __result;                                                       \
})

imx6_suspend()

The ʻimx6_suspend ‘function is included as an assembler.

Oh, I put it in a memory refresh myself. It will be helpful.

arch/arm/mach-imx/suspend-imx6.S


ENTRY(imx6_suspend)
        ldr     r1, [r0, #PM_INFO_PBASE_OFFSET]
        ldr     r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET]
        ldr     r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]
        ldr     r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]

<Omitted>
        /*
         * put DDR explicitly into self-refresh and
         * disable automatic power savings.
         */
        ldr     r7, [r11, #MX6Q_MMDC_MAPSR]
        orr     r7, r7, #0x1
        str     r7, [r11, #MX6Q_MMDC_MAPSR]

<Omitted>

        /* Zzz, enter stop mode */
        wfi
        nop
        nop
        nop
        nop

<Omitted>

Summary (repost)

--I briefly checked the power saving implementation method based on i.mx6 of Linux Kernel 4.1. --Check the implementation of WFI by executing sleep to mem and putting the memory in the self-refresh state. --Use the function provided by Linux as the function to copy the prepared function to the dedicated SRAM.

that's all.

Recommended Posts

Confirmed Vendor-dependent power saving implementation method of Linux Kernel based on i.mx6.
[Python] Implementation of Nelder–Mead method and saving of GIF images by matplotlib