123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- /*
- * Copyright (c) 2013 ARM/Linaro
- *
- * Authors: Daniel Lezcano <daniel.lezcano@linaro.org>
- * Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
- * Nicolas Pitre <nicolas.pitre@linaro.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Maintainer: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
- * Maintainer: Daniel Lezcano <daniel.lezcano@linaro.org>
- */
- #include <linux/cpuidle.h>
- #include <linux/cpu_pm.h>
- #include <linux/slab.h>
- #include <linux/of.h>
- #include <asm/cpu.h>
- #include <asm/cputype.h>
- #include <asm/cpuidle.h>
- #include <asm/mcpm.h>
- #include <asm/smp_plat.h>
- #include <asm/suspend.h>
- #include "dt_idle_states.h"
- static int bl_enter_powerdown(struct cpuidle_device *dev,
- struct cpuidle_driver *drv, int idx);
- /*
- * NB: Owing to current menu governor behaviour big and LITTLE
- * index 1 states have to define exit_latency and target_residency for
- * cluster state since, when all CPUs in a cluster hit it, the cluster
- * can be shutdown. This means that when a single CPU enters this state
- * the exit_latency and target_residency values are somewhat overkill.
- * There is no notion of cluster states in the menu governor, so CPUs
- * have to define CPU states where possibly the cluster will be shutdown
- * depending on the state of other CPUs. idle states entry and exit happen
- * at random times; however the cluster state provides target_residency
- * values as if all CPUs in a cluster enter the state at once; this is
- * somewhat optimistic and behaviour should be fixed either in the governor
- * or in the MCPM back-ends.
- * To make this driver 100% generic the number of states and the exit_latency
- * target_residency values must be obtained from device tree bindings.
- *
- * exit_latency: refers to the TC2 vexpress test chip and depends on the
- * current cluster operating point. It is the time it takes to get the CPU
- * up and running when the CPU is powered up on cluster wake-up from shutdown.
- * Current values for big and LITTLE clusters are provided for clusters
- * running at default operating points.
- *
- * target_residency: it is the minimum amount of time the cluster has
- * to be down to break even in terms of power consumption. cluster
- * shutdown has inherent dynamic power costs (L2 writebacks to DRAM
- * being the main factor) that depend on the current operating points.
- * The current values for both clusters are provided for a CPU whose half
- * of L2 lines are dirty and require cleaning to DRAM, and takes into
- * account leakage static power values related to the vexpress TC2 testchip.
- */
- static struct cpuidle_driver bl_idle_little_driver = {
- .name = "little_idle",
- .owner = THIS_MODULE,
- .states[0] = ARM_CPUIDLE_WFI_STATE,
- .states[1] = {
- .enter = bl_enter_powerdown,
- .exit_latency = 700,
- .target_residency = 2500,
- .flags = CPUIDLE_FLAG_TIMER_STOP,
- .name = "C1",
- .desc = "ARM little-cluster power down",
- },
- .state_count = 2,
- };
- static const struct of_device_id bl_idle_state_match[] __initconst = {
- { .compatible = "arm,idle-state",
- .data = bl_enter_powerdown },
- { },
- };
- static struct cpuidle_driver bl_idle_big_driver = {
- .name = "big_idle",
- .owner = THIS_MODULE,
- .states[0] = ARM_CPUIDLE_WFI_STATE,
- .states[1] = {
- .enter = bl_enter_powerdown,
- .exit_latency = 500,
- .target_residency = 2000,
- .flags = CPUIDLE_FLAG_TIMER_STOP,
- .name = "C1",
- .desc = "ARM big-cluster power down",
- },
- .state_count = 2,
- };
- /*
- * notrace prevents trace shims from getting inserted where they
- * should not. Global jumps and ldrex/strex must not be inserted
- * in power down sequences where caches and MMU may be turned off.
- */
- static int notrace bl_powerdown_finisher(unsigned long arg)
- {
- /* MCPM works with HW CPU identifiers */
- unsigned int mpidr = read_cpuid_mpidr();
- unsigned int cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
- unsigned int cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
- mcpm_set_entry_vector(cpu, cluster, cpu_resume);
- mcpm_cpu_suspend();
- /* return value != 0 means failure */
- return 1;
- }
- /**
- * bl_enter_powerdown - Programs CPU to enter the specified state
- * @dev: cpuidle device
- * @drv: The target state to be programmed
- * @idx: state index
- *
- * Called from the CPUidle framework to program the device to the
- * specified target state selected by the governor.
- */
- static int bl_enter_powerdown(struct cpuidle_device *dev,
- struct cpuidle_driver *drv, int idx)
- {
- cpu_pm_enter();
- cpu_suspend(0, bl_powerdown_finisher);
- /* signals the MCPM core that CPU is out of low power state */
- mcpm_cpu_powered_up();
- cpu_pm_exit();
- return idx;
- }
- static int __init bl_idle_driver_init(struct cpuidle_driver *drv, int part_id)
- {
- struct cpumask *cpumask;
- int cpu;
- cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
- if (!cpumask)
- return -ENOMEM;
- for_each_possible_cpu(cpu)
- if (smp_cpuid_part(cpu) == part_id)
- cpumask_set_cpu(cpu, cpumask);
- drv->cpumask = cpumask;
- return 0;
- }
- static const struct of_device_id compatible_machine_match[] = {
- { .compatible = "arm,vexpress,v2p-ca15_a7" },
- { .compatible = "samsung,exynos5420" },
- { .compatible = "samsung,exynos5800" },
- {},
- };
- static int __init bl_idle_init(void)
- {
- int ret;
- struct device_node *root = of_find_node_by_path("/");
- if (!root)
- return -ENODEV;
- /*
- * Initialize the driver just for a compliant set of machines
- */
- if (!of_match_node(compatible_machine_match, root))
- return -ENODEV;
- if (!mcpm_is_available())
- return -EUNATCH;
- /*
- * For now the differentiation between little and big cores
- * is based on the part number. A7 cores are considered little
- * cores, A15 are considered big cores. This distinction may
- * evolve in the future with a more generic matching approach.
- */
- ret = bl_idle_driver_init(&bl_idle_little_driver,
- ARM_CPU_PART_CORTEX_A7);
- if (ret)
- return ret;
- ret = bl_idle_driver_init(&bl_idle_big_driver, ARM_CPU_PART_CORTEX_A15);
- if (ret)
- goto out_uninit_little;
- /* Start at index 1, index 0 standard WFI */
- ret = dt_init_idle_driver(&bl_idle_big_driver, bl_idle_state_match, 1);
- if (ret < 0)
- goto out_uninit_big;
- /* Start at index 1, index 0 standard WFI */
- ret = dt_init_idle_driver(&bl_idle_little_driver,
- bl_idle_state_match, 1);
- if (ret < 0)
- goto out_uninit_big;
- ret = cpuidle_register(&bl_idle_little_driver, NULL);
- if (ret)
- goto out_uninit_big;
- ret = cpuidle_register(&bl_idle_big_driver, NULL);
- if (ret)
- goto out_unregister_little;
- return 0;
- out_unregister_little:
- cpuidle_unregister(&bl_idle_little_driver);
- out_uninit_big:
- kfree(bl_idle_big_driver.cpumask);
- out_uninit_little:
- kfree(bl_idle_little_driver.cpumask);
- return ret;
- }
- device_initcall(bl_idle_init);
|