0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

i.mx6ベースで、Linux KernelのVendor依存な省電力実装方法を確認。

Posted at

初めに

mach-imx/pm-imx6 のコードを基に、省電力モード実装するときに何をするべきか、というのをまとめてみる。

TL;DR

  • Linux Kernel 4.1の、i.mx6をベースに省電力実装方法を簡単に確認してみた。
  • sleep to memを実行し、メモリをセルフリフレッシュ状態に入れてWFIする実装を確認。
  • 用意していた関数を、専用のSRAMへコピーする関数は、Linux側が提供しているものを使う。
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

サポートしているのは、STANDBYと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

enterの中身も非常にシンプルでわかりやすい!

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(); は、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()

一方、cpu_suspend() は引数で与えた関数を実行するらしい。

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()

この時の引数になるのは、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()

ここから呼び出されている、imx6_suspend_in_orcam_fn() は、関数ポインタで定義されている。

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);

起動時の初期化時点で、ocram上にimx6_suspend関数をコピーしている。

ocramは、コードを見てもわかる通り、mmi-sram、つまり内蔵SRAM。
ここに置かれているデータは基本的にはDDRがメモリリフレッシュ状態になっても参照できる。
ここにスリープの実装が入っている、と。

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);

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

fncpy()

関数を別メモリアドレス上にコピーする fncpy()は、汎用的に使える関数として定義されている。

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()

`imx6_suspend‘関数は、アセンブラとして入っている。

おお、自分でメモリリフレッシュに入れたりとかしている。参考になる。

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]

<略>
        /*
         * 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]

<略>

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

<略>

まとめ(再掲)

  • Linux Kernel 4.1の、i.mx6をベースに省電力実装方法を簡単に確認してみた。
  • sleep to memを実行し、メモリをセルフリフレッシュ状態に入れてWFIする実装を確認。
  • 用意していた関数を、専用のSRAMへコピーする関数は、Linux側が提供しているものを使う。

以上です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?