linux/drivers/cpuidle/cpuidle-big_little.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2013 ARM/Linaro
   4 *
   5 * Authors: Daniel Lezcano <daniel.lezcano@linaro.org>
   6 *          Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
   7 *          Nicolas Pitre <nicolas.pitre@linaro.org>
   8 *
   9 * Maintainer: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
  10 * Maintainer: Daniel Lezcano <daniel.lezcano@linaro.org>
  11 */
  12#include <linux/cpuidle.h>
  13#include <linux/cpu_pm.h>
  14#include <linux/slab.h>
  15#include <linux/of.h>
  16
  17#include <asm/cpu.h>
  18#include <asm/cputype.h>
  19#include <asm/cpuidle.h>
  20#include <asm/mcpm.h>
  21#include <asm/smp_plat.h>
  22#include <asm/suspend.h>
  23
  24#include "dt_idle_states.h"
  25
  26static int bl_enter_powerdown(struct cpuidle_device *dev,
  27                              struct cpuidle_driver *drv, int idx);
  28
  29/*
  30 * NB: Owing to current menu governor behaviour big and LITTLE
  31 * index 1 states have to define exit_latency and target_residency for
  32 * cluster state since, when all CPUs in a cluster hit it, the cluster
  33 * can be shutdown. This means that when a single CPU enters this state
  34 * the exit_latency and target_residency values are somewhat overkill.
  35 * There is no notion of cluster states in the menu governor, so CPUs
  36 * have to define CPU states where possibly the cluster will be shutdown
  37 * depending on the state of other CPUs. idle states entry and exit happen
  38 * at random times; however the cluster state provides target_residency
  39 * values as if all CPUs in a cluster enter the state at once; this is
  40 * somewhat optimistic and behaviour should be fixed either in the governor
  41 * or in the MCPM back-ends.
  42 * To make this driver 100% generic the number of states and the exit_latency
  43 * target_residency values must be obtained from device tree bindings.
  44 *
  45 * exit_latency: refers to the TC2 vexpress test chip and depends on the
  46 * current cluster operating point. It is the time it takes to get the CPU
  47 * up and running when the CPU is powered up on cluster wake-up from shutdown.
  48 * Current values for big and LITTLE clusters are provided for clusters
  49 * running at default operating points.
  50 *
  51 * target_residency: it is the minimum amount of time the cluster has
  52 * to be down to break even in terms of power consumption. cluster
  53 * shutdown has inherent dynamic power costs (L2 writebacks to DRAM
  54 * being the main factor) that depend on the current operating points.
  55 * The current values for both clusters are provided for a CPU whose half
  56 * of L2 lines are dirty and require cleaning to DRAM, and takes into
  57 * account leakage static power values related to the vexpress TC2 testchip.
  58 */
  59static struct cpuidle_driver bl_idle_little_driver = {
  60        .name = "little_idle",
  61        .owner = THIS_MODULE,
  62        .states[0] = ARM_CPUIDLE_WFI_STATE,
  63        .states[1] = {
  64                .enter                  = bl_enter_powerdown,
  65                .exit_latency           = 700,
  66                .target_residency       = 2500,
  67                .flags                  = CPUIDLE_FLAG_TIMER_STOP,
  68                .name                   = "C1",
  69                .desc                   = "ARM little-cluster power down",
  70        },
  71        .state_count = 2,
  72};
  73
  74static const struct of_device_id bl_idle_state_match[] __initconst = {
  75        { .compatible = "arm,idle-state",
  76          .data = bl_enter_powerdown },
  77        { },
  78};
  79
  80static struct cpuidle_driver bl_idle_big_driver = {
  81        .name = "big_idle",
  82        .owner = THIS_MODULE,
  83        .states[0] = ARM_CPUIDLE_WFI_STATE,
  84        .states[1] = {
  85                .enter                  = bl_enter_powerdown,
  86                .exit_latency           = 500,
  87                .target_residency       = 2000,
  88                .flags                  = CPUIDLE_FLAG_TIMER_STOP,
  89                .name                   = "C1",
  90                .desc                   = "ARM big-cluster power down",
  91        },
  92        .state_count = 2,
  93};
  94
  95/*
  96 * notrace prevents trace shims from getting inserted where they
  97 * should not. Global jumps and ldrex/strex must not be inserted
  98 * in power down sequences where caches and MMU may be turned off.
  99 */
 100static int notrace bl_powerdown_finisher(unsigned long arg)
 101{
 102        /* MCPM works with HW CPU identifiers */
 103        unsigned int mpidr = read_cpuid_mpidr();
 104        unsigned int cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
 105        unsigned int cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
 106
 107        mcpm_set_entry_vector(cpu, cluster, cpu_resume);
 108        mcpm_cpu_suspend();
 109
 110        /* return value != 0 means failure */
 111        return 1;
 112}
 113
 114/**
 115 * bl_enter_powerdown - Programs CPU to enter the specified state
 116 * @dev: cpuidle device
 117 * @drv: The target state to be programmed
 118 * @idx: state index
 119 *
 120 * Called from the CPUidle framework to program the device to the
 121 * specified target state selected by the governor.
 122 */
 123static int bl_enter_powerdown(struct cpuidle_device *dev,
 124                                struct cpuidle_driver *drv, int idx)
 125{
 126        cpu_pm_enter();
 127
 128        cpu_suspend(0, bl_powerdown_finisher);
 129
 130        /* signals the MCPM core that CPU is out of low power state */
 131        mcpm_cpu_powered_up();
 132
 133        cpu_pm_exit();
 134
 135        return idx;
 136}
 137
 138static int __init bl_idle_driver_init(struct cpuidle_driver *drv, int part_id)
 139{
 140        struct cpumask *cpumask;
 141        int cpu;
 142
 143        cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
 144        if (!cpumask)
 145                return -ENOMEM;
 146
 147        for_each_possible_cpu(cpu)
 148                if (smp_cpuid_part(cpu) == part_id)
 149                        cpumask_set_cpu(cpu, cpumask);
 150
 151        drv->cpumask = cpumask;
 152
 153        return 0;
 154}
 155
 156static const struct of_device_id compatible_machine_match[] = {
 157        { .compatible = "arm,vexpress,v2p-ca15_a7" },
 158        { .compatible = "google,peach" },
 159        {},
 160};
 161
 162static int __init bl_idle_init(void)
 163{
 164        int ret;
 165        struct device_node *root = of_find_node_by_path("/");
 166        const struct of_device_id *match_id;
 167
 168        if (!root)
 169                return -ENODEV;
 170
 171        /*
 172         * Initialize the driver just for a compliant set of machines
 173         */
 174        match_id = of_match_node(compatible_machine_match, root);
 175
 176        of_node_put(root);
 177
 178        if (!match_id)
 179                return -ENODEV;
 180
 181        if (!mcpm_is_available())
 182                return -EUNATCH;
 183
 184        /*
 185         * For now the differentiation between little and big cores
 186         * is based on the part number. A7 cores are considered little
 187         * cores, A15 are considered big cores. This distinction may
 188         * evolve in the future with a more generic matching approach.
 189         */
 190        ret = bl_idle_driver_init(&bl_idle_little_driver,
 191                                  ARM_CPU_PART_CORTEX_A7);
 192        if (ret)
 193                return ret;
 194
 195        ret = bl_idle_driver_init(&bl_idle_big_driver, ARM_CPU_PART_CORTEX_A15);
 196        if (ret)
 197                goto out_uninit_little;
 198
 199        /* Start at index 1, index 0 standard WFI */
 200        ret = dt_init_idle_driver(&bl_idle_big_driver, bl_idle_state_match, 1);
 201        if (ret < 0)
 202                goto out_uninit_big;
 203
 204        /* Start at index 1, index 0 standard WFI */
 205        ret = dt_init_idle_driver(&bl_idle_little_driver,
 206                                  bl_idle_state_match, 1);
 207        if (ret < 0)
 208                goto out_uninit_big;
 209
 210        ret = cpuidle_register(&bl_idle_little_driver, NULL);
 211        if (ret)
 212                goto out_uninit_big;
 213
 214        ret = cpuidle_register(&bl_idle_big_driver, NULL);
 215        if (ret)
 216                goto out_unregister_little;
 217
 218        return 0;
 219
 220out_unregister_little:
 221        cpuidle_unregister(&bl_idle_little_driver);
 222out_uninit_big:
 223        kfree(bl_idle_big_driver.cpumask);
 224out_uninit_little:
 225        kfree(bl_idle_little_driver.cpumask);
 226
 227        return ret;
 228}
 229device_initcall(bl_idle_init);
 230