summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/cpuidle/Kconfig11
-rw-r--r--drivers/cpuidle/governors/Makefile1
-rw-r--r--drivers/cpuidle/governors/mobile.c151
3 files changed, 162 insertions, 1 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index 8caccbbd7353..547cfc204b37 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -4,7 +4,7 @@ config CPU_IDLE
bool "CPU idle PM support"
default y if ACPI || PPC_PSERIES
select CPU_IDLE_GOV_LADDER if (!NO_HZ && !NO_HZ_IDLE)
- select CPU_IDLE_GOV_MENU if (NO_HZ || NO_HZ_IDLE) && !CPU_IDLE_GOV_TEO
+ select CPU_IDLE_GOV_MENU if (NO_HZ || NO_HZ_IDLE) && !CPU_IDLE_GOV_TEO && !CPU_IDLE_GOV_MOBILE
help
CPU idle is a generic framework for supporting software-controlled
idle processor power management. It includes modular cross-platform
@@ -32,6 +32,15 @@ config CPU_IDLE_GOV_TEO
Some workloads benefit from using it and it generally should be safe
to use. Say Y here if you are not happy with the alternatives.
+config CPU_IDLE_GOV_MOBILE
+ bool "Mobile governor"
+ select IRQ_TIMINGS
+ help
+ The mobile governor is based on irq timings measurements and
+ pattern research combined with the next timer. This governor
+ suits very well on embedded systems where the interrupts are
+ grouped on a single core and the power is the priority.
+
config DT_IDLE_STATES
bool
diff --git a/drivers/cpuidle/governors/Makefile b/drivers/cpuidle/governors/Makefile
index 4d8aff5248a8..1e1feebf27d8 100644
--- a/drivers/cpuidle/governors/Makefile
+++ b/drivers/cpuidle/governors/Makefile
@@ -5,3 +5,4 @@
obj-$(CONFIG_CPU_IDLE_GOV_LADDER) += ladder.o
obj-$(CONFIG_CPU_IDLE_GOV_MENU) += menu.o
obj-$(CONFIG_CPU_IDLE_GOV_TEO) += teo.o
+obj-$(CONFIG_CPU_IDLE_GOV_MOBILE) += mobile.o
diff --git a/drivers/cpuidle/governors/mobile.c b/drivers/cpuidle/governors/mobile.c
new file mode 100644
index 000000000000..85cb2155d5ca
--- /dev/null
+++ b/drivers/cpuidle/governors/mobile.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019, Linaro Ltd
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ */
+#include <linux/cpuidle.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/tick.h>
+#include <linux/interrupt.h>
+#include <linux/sched/clock.h>
+
+struct mobile_device {
+ u64 idle_ema_avg;
+ u64 idle_total;
+ unsigned long last_jiffies;
+};
+
+#define EMA_ALPHA_VAL 64
+#define EMA_ALPHA_SHIFT 7
+#define MAX_RESCHED_INTERVAL_MS 100
+
+static DEFINE_PER_CPU(struct mobile_device, mobile_devices);
+
+static int mobile_ema_new(s64 value, s64 ema_old)
+{
+ if (likely(ema_old))
+ return ema_old + (((value - ema_old) * EMA_ALPHA_VAL) >>
+ EMA_ALPHA_SHIFT);
+ return value;
+}
+
+static void mobile_reflect(struct cpuidle_device *dev, int index)
+{
+ struct mobile_device *mobile_dev = this_cpu_ptr(&mobile_devices);
+ struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
+ struct cpuidle_state *s = &drv->states[index];
+ int residency;
+
+ /*
+ * The idle task was not rescheduled since
+ * MAX_RESCHED_INTERVAL_MS, let's consider the duration is
+ * long enough to clear our stats.
+ */
+ if (time_after(jiffies, mobile_dev->last_jiffies +
+ msecs_to_jiffies(MAX_RESCHED_INTERVAL_MS)))
+ mobile_dev->idle_ema_avg = 0;
+
+ /*
+ * Sum all the residencies in order to compute the total
+ * duration of the idle task.
+ */
+ residency = dev->last_residency - s->exit_latency;
+ if (residency > 0)
+ mobile_dev->idle_total += residency;
+
+ /*
+ * We exited the idle state with the need_resched() flag, the
+ * idle task will be rescheduled, so store the duration the
+ * idle task was scheduled in an exponential moving average and
+ * reset the total of the idle duration.
+ */
+ if (need_resched()) {
+ mobile_dev->idle_ema_avg = mobile_ema_new(mobile_dev->idle_total,
+ mobile_dev->idle_ema_avg);
+ mobile_dev->idle_total = 0;
+ mobile_dev->last_jiffies = jiffies;
+ }
+}
+
+static int mobile_select(struct cpuidle_driver *drv, struct cpuidle_device *dev,
+ bool *stop_tick)
+{
+ struct mobile_device *mobile_dev = this_cpu_ptr(&mobile_devices);
+ int latency_req = cpuidle_governor_latency_req(dev->cpu);
+ int i, index = 0;
+ ktime_t delta_next;
+ u64 now, irq_length, timer_length;
+ u64 idle_duration_us;
+
+ /*
+ * Get the present time as reference for the next steps
+ */
+ now = local_clock();
+
+ /*
+ * Get the next interrupt event giving the 'now' as a
+ * reference, if the next event appears to have already
+ * expired then we get the 'now' returned which ends up with a
+ * zero duration.
+ */
+ irq_length = irq_timings_next_event(now) - now;
+
+ /*
+ * Get the timer duration before expiration.
+ */
+ timer_length = ktime_to_ns(tick_nohz_get_sleep_length(&delta_next));
+
+ /*
+ * Get the smallest duration between the timer and the irq next event.
+ */
+ idle_duration_us = min_t(u64, irq_length, timer_length) / NSEC_PER_USEC;
+
+ /*
+ * Get the idle task duration average if the information is
+ * available.
+ */
+ if (mobile_dev->idle_ema_avg)
+ idle_duration_us = min_t(u64, idle_duration_us,
+ mobile_dev->idle_ema_avg);
+
+ for (i = 0; i < drv->state_count; i++) {
+ struct cpuidle_state *s = &drv->states[i];
+ struct cpuidle_state_usage *su = &dev->states_usage[i];
+
+ if (s->disabled || su->disable)
+ continue;
+
+ if (s->exit_latency > latency_req)
+ break;
+
+ if (s->exit_latency >= idle_duration_us)
+ break;
+
+ if (s->target_residency > (idle_duration_us - s->exit_latency))
+ break;
+
+ index = i;
+ }
+
+ if (!index)
+ *stop_tick = false;
+
+ return index;
+}
+
+static struct cpuidle_governor mobile_governor = {
+ .name = "mobile",
+ .rating = 20,
+ .select = mobile_select,
+ .reflect = mobile_reflect,
+};
+
+static int __init init_governor(void)
+{
+ irq_timings_enable();
+ return cpuidle_register_governor(&mobile_governor);
+}
+
+postcore_initcall(init_governor);