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>
--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.