summaryrefslogtreecommitdiff
path: root/drivers/cpuidle
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/cpuidle')
-rw-r--r--drivers/cpuidle/Kconfig.arm10
-rw-r--r--drivers/cpuidle/Makefile5
-rw-r--r--drivers/cpuidle/cpuidle-powernv.c14
-rw-r--r--drivers/cpuidle/cpuidle-psci-domain.c74
-rw-r--r--drivers/cpuidle/cpuidle-psci.c141
-rw-r--r--drivers/cpuidle/cpuidle-psci.h11
-rw-r--r--drivers/cpuidle/cpuidle-pseries.c187
-rw-r--r--drivers/cpuidle/cpuidle-tegra.c8
8 files changed, 339 insertions, 111 deletions
diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
index 51a7e89085c0..0844fadc4be8 100644
--- a/drivers/cpuidle/Kconfig.arm
+++ b/drivers/cpuidle/Kconfig.arm
@@ -23,6 +23,16 @@ config ARM_PSCI_CPUIDLE
It provides an idle driver that is capable of detecting and
managing idle states through the PSCI firmware interface.
+config ARM_PSCI_CPUIDLE_DOMAIN
+ bool "PSCI CPU idle Domain"
+ depends on ARM_PSCI_CPUIDLE
+ depends on PM_GENERIC_DOMAINS_OF
+ default y
+ help
+ Select this to enable the PSCI based CPUidle driver to use PM domains,
+ which is needed to support the hierarchical DT based layout of the
+ idle states.
+
config ARM_BIG_LITTLE_CPUIDLE
bool "Support for ARM big.LITTLE processors"
depends on ARCH_VEXPRESS_TC2_PM || ARCH_EXYNOS || COMPILE_TEST
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index f07800cbb43f..26bbc5e74123 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -21,9 +21,8 @@ obj-$(CONFIG_ARM_U8500_CPUIDLE) += cpuidle-ux500.o
obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o
obj-$(CONFIG_ARM_EXYNOS_CPUIDLE) += cpuidle-exynos.o
obj-$(CONFIG_ARM_CPUIDLE) += cpuidle-arm.o
-obj-$(CONFIG_ARM_PSCI_CPUIDLE) += cpuidle_psci.o
-cpuidle_psci-y := cpuidle-psci.o
-cpuidle_psci-$(CONFIG_PM_GENERIC_DOMAINS_OF) += cpuidle-psci-domain.o
+obj-$(CONFIG_ARM_PSCI_CPUIDLE) += cpuidle-psci.o
+obj-$(CONFIG_ARM_PSCI_CPUIDLE_DOMAIN) += cpuidle-psci-domain.o
obj-$(CONFIG_ARM_TEGRA_CPUIDLE) += cpuidle-tegra.o
obj-$(CONFIG_ARM_QCOM_SPM_CPUIDLE) += cpuidle-qcom-spm.o
diff --git a/drivers/cpuidle/cpuidle-powernv.c b/drivers/cpuidle/cpuidle-powernv.c
index 1b299e801f74..addaa6e6718b 100644
--- a/drivers/cpuidle/cpuidle-powernv.c
+++ b/drivers/cpuidle/cpuidle-powernv.c
@@ -244,20 +244,6 @@ static inline void add_powernv_state(int index, const char *name,
stop_psscr_table[index].mask = psscr_mask;
}
-/*
- * Returns 0 if prop1_len == prop2_len. Else returns -1
- */
-static inline int validate_dt_prop_sizes(const char *prop1, int prop1_len,
- const char *prop2, int prop2_len)
-{
- if (prop1_len == prop2_len)
- return 0;
-
- pr_warn("cpuidle-powernv: array sizes don't match for %s and %s\n",
- prop1, prop2);
- return -1;
-}
-
extern u32 pnv_get_supported_cpuidle_states(void);
static int powernv_add_idle_states(void)
{
diff --git a/drivers/cpuidle/cpuidle-psci-domain.c b/drivers/cpuidle/cpuidle-psci-domain.c
index 423f03bbeb74..b6e9649ab0da 100644
--- a/drivers/cpuidle/cpuidle-psci-domain.c
+++ b/drivers/cpuidle/cpuidle-psci-domain.c
@@ -12,6 +12,7 @@
#include <linux/cpu.h>
#include <linux/device.h>
#include <linux/kernel.h>
+#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/psci.h>
@@ -26,7 +27,7 @@ struct psci_pd_provider {
};
static LIST_HEAD(psci_pd_providers);
-static bool osi_mode_enabled __initdata;
+static bool psci_pd_allow_domain_state;
static int psci_pd_power_off(struct generic_pm_domain *pd)
{
@@ -36,6 +37,9 @@ static int psci_pd_power_off(struct generic_pm_domain *pd)
if (!state->data)
return 0;
+ if (!psci_pd_allow_domain_state)
+ return -EBUSY;
+
/* OSI mode is enabled, set the corresponding domain state. */
pd_state = state->data;
psci_set_domain_state(*pd_state);
@@ -43,8 +47,8 @@ static int psci_pd_power_off(struct generic_pm_domain *pd)
return 0;
}
-static int __init psci_pd_parse_state_nodes(struct genpd_power_state *states,
- int state_count)
+static int psci_pd_parse_state_nodes(struct genpd_power_state *states,
+ int state_count)
{
int i, ret;
u32 psci_state, *psci_state_buf;
@@ -73,7 +77,7 @@ free_state:
return ret;
}
-static int __init psci_pd_parse_states(struct device_node *np,
+static int psci_pd_parse_states(struct device_node *np,
struct genpd_power_state **states, int *state_count)
{
int ret;
@@ -101,7 +105,7 @@ static void psci_pd_free_states(struct genpd_power_state *states,
kfree(states);
}
-static int __init psci_pd_init(struct device_node *np)
+static int psci_pd_init(struct device_node *np)
{
struct generic_pm_domain *pd;
struct psci_pd_provider *pd_provider;
@@ -168,7 +172,7 @@ out:
return ret;
}
-static void __init psci_pd_remove(void)
+static void psci_pd_remove(void)
{
struct psci_pd_provider *pd_provider, *it;
struct generic_pm_domain *genpd;
@@ -186,7 +190,7 @@ static void __init psci_pd_remove(void)
}
}
-static int __init psci_pd_init_topology(struct device_node *np, bool add)
+static int psci_pd_init_topology(struct device_node *np, bool add)
{
struct device_node *node;
struct of_phandle_args child, parent;
@@ -212,24 +216,33 @@ static int __init psci_pd_init_topology(struct device_node *np, bool add)
return 0;
}
-static int __init psci_pd_add_topology(struct device_node *np)
+static int psci_pd_add_topology(struct device_node *np)
{
return psci_pd_init_topology(np, true);
}
-static void __init psci_pd_remove_topology(struct device_node *np)
+static void psci_pd_remove_topology(struct device_node *np)
{
psci_pd_init_topology(np, false);
}
-static const struct of_device_id psci_of_match[] __initconst = {
+static void psci_cpuidle_domain_sync_state(struct device *dev)
+{
+ /*
+ * All devices have now been attached/probed to the PM domain topology,
+ * hence it's fine to allow domain states to be picked.
+ */
+ psci_pd_allow_domain_state = true;
+}
+
+static const struct of_device_id psci_of_match[] = {
{ .compatible = "arm,psci-1.0" },
{}
};
-static int __init psci_idle_init_domains(void)
+static int psci_cpuidle_domain_probe(struct platform_device *pdev)
{
- struct device_node *np = of_find_matching_node(NULL, psci_of_match);
+ struct device_node *np = pdev->dev.of_node;
struct device_node *node;
int ret = 0, pd_count = 0;
@@ -238,7 +251,7 @@ static int __init psci_idle_init_domains(void)
/* Currently limit the hierarchical topology to be used in OSI mode. */
if (!psci_has_osi_support())
- goto out;
+ return 0;
/*
* Parse child nodes for the "#power-domain-cells" property and
@@ -257,7 +270,7 @@ static int __init psci_idle_init_domains(void)
/* Bail out if not using the hierarchical CPU topology. */
if (!pd_count)
- goto out;
+ return 0;
/* Link genpd masters/subdomains to model the CPU topology. */
ret = psci_pd_add_topology(np);
@@ -272,10 +285,8 @@ static int __init psci_idle_init_domains(void)
goto remove_pd;
}
- osi_mode_enabled = true;
- of_node_put(np);
pr_info("Initialized CPU PM domain topology\n");
- return pd_count;
+ return 0;
put_node:
of_node_put(node);
@@ -283,19 +294,28 @@ remove_pd:
if (pd_count)
psci_pd_remove();
pr_err("failed to create CPU PM domains ret=%d\n", ret);
-out:
- of_node_put(np);
return ret;
}
+
+static struct platform_driver psci_cpuidle_domain_driver = {
+ .probe = psci_cpuidle_domain_probe,
+ .driver = {
+ .name = "psci-cpuidle-domain",
+ .of_match_table = psci_of_match,
+ .sync_state = psci_cpuidle_domain_sync_state,
+ },
+};
+
+static int __init psci_idle_init_domains(void)
+{
+ return platform_driver_register(&psci_cpuidle_domain_driver);
+}
subsys_initcall(psci_idle_init_domains);
-struct device __init *psci_dt_attach_cpu(int cpu)
+struct device *psci_dt_attach_cpu(int cpu)
{
struct device *dev;
- if (!osi_mode_enabled)
- return NULL;
-
dev = dev_pm_domain_attach_by_name(get_cpu_device(cpu), "psci");
if (IS_ERR_OR_NULL(dev))
return dev;
@@ -306,3 +326,11 @@ struct device __init *psci_dt_attach_cpu(int cpu)
return dev;
}
+
+void psci_dt_detach_cpu(struct device *dev)
+{
+ if (IS_ERR_OR_NULL(dev))
+ return;
+
+ dev_pm_domain_detach(dev, false);
+}
diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c
index 3806f911b61c..74463841805f 100644
--- a/drivers/cpuidle/cpuidle-psci.c
+++ b/drivers/cpuidle/cpuidle-psci.c
@@ -17,9 +17,11 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/psci.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
+#include <linux/string.h>
#include <asm/cpuidle.h>
@@ -33,7 +35,7 @@ struct psci_cpuidle_data {
static DEFINE_PER_CPU_READ_MOSTLY(struct psci_cpuidle_data, psci_cpuidle_data);
static DEFINE_PER_CPU(u32, domain_state);
-static bool psci_cpuidle_use_cpuhp __initdata;
+static bool psci_cpuidle_use_cpuhp;
void psci_set_domain_state(u32 state)
{
@@ -104,7 +106,7 @@ static int psci_idle_cpuhp_down(unsigned int cpu)
return 0;
}
-static void __init psci_idle_init_cpuhp(void)
+static void psci_idle_init_cpuhp(void)
{
int err;
@@ -127,30 +129,13 @@ static int psci_enter_idle_state(struct cpuidle_device *dev,
return psci_enter_state(idx, state[idx]);
}
-static struct cpuidle_driver psci_idle_driver __initdata = {
- .name = "psci_idle",
- .owner = THIS_MODULE,
- /*
- * PSCI idle states relies on architectural WFI to
- * be represented as state index 0.
- */
- .states[0] = {
- .enter = psci_enter_idle_state,
- .exit_latency = 1,
- .target_residency = 1,
- .power_usage = UINT_MAX,
- .name = "WFI",
- .desc = "ARM WFI",
- }
-};
-
-static const struct of_device_id psci_idle_state_match[] __initconst = {
+static const struct of_device_id psci_idle_state_match[] = {
{ .compatible = "arm,idle-state",
.data = psci_enter_idle_state },
{ },
};
-int __init psci_dt_parse_state_node(struct device_node *np, u32 *state)
+int psci_dt_parse_state_node(struct device_node *np, u32 *state)
{
int err = of_property_read_u32(np, "arm,psci-suspend-param", state);
@@ -167,9 +152,9 @@ int __init psci_dt_parse_state_node(struct device_node *np, u32 *state)
return 0;
}
-static int __init psci_dt_cpu_init_topology(struct cpuidle_driver *drv,
- struct psci_cpuidle_data *data,
- unsigned int state_count, int cpu)
+static int psci_dt_cpu_init_topology(struct cpuidle_driver *drv,
+ struct psci_cpuidle_data *data,
+ unsigned int state_count, int cpu)
{
/* Currently limit the hierarchical topology to be used in OSI mode. */
if (!psci_has_osi_support())
@@ -190,9 +175,9 @@ static int __init psci_dt_cpu_init_topology(struct cpuidle_driver *drv,
return 0;
}
-static int __init psci_dt_cpu_init_idle(struct cpuidle_driver *drv,
- struct device_node *cpu_node,
- unsigned int state_count, int cpu)
+static int psci_dt_cpu_init_idle(struct device *dev, struct cpuidle_driver *drv,
+ struct device_node *cpu_node,
+ unsigned int state_count, int cpu)
{
int i, ret = 0;
u32 *psci_states;
@@ -200,7 +185,8 @@ static int __init psci_dt_cpu_init_idle(struct cpuidle_driver *drv,
struct psci_cpuidle_data *data = per_cpu_ptr(&psci_cpuidle_data, cpu);
state_count++; /* Add WFI state too */
- psci_states = kcalloc(state_count, sizeof(*psci_states), GFP_KERNEL);
+ psci_states = devm_kcalloc(dev, state_count, sizeof(*psci_states),
+ GFP_KERNEL);
if (!psci_states)
return -ENOMEM;
@@ -213,32 +199,26 @@ static int __init psci_dt_cpu_init_idle(struct cpuidle_driver *drv,
of_node_put(state_node);
if (ret)
- goto free_mem;
+ return ret;
pr_debug("psci-power-state %#x index %d\n", psci_states[i], i);
}
- if (i != state_count) {
- ret = -ENODEV;
- goto free_mem;
- }
+ if (i != state_count)
+ return -ENODEV;
/* Initialize optional data, used for the hierarchical topology. */
ret = psci_dt_cpu_init_topology(drv, data, state_count, cpu);
if (ret < 0)
- goto free_mem;
+ return ret;
/* Idle states parsed correctly, store them in the per-cpu struct. */
data->psci_states = psci_states;
return 0;
-
-free_mem:
- kfree(psci_states);
- return ret;
}
-static __init int psci_cpu_init_idle(struct cpuidle_driver *drv,
- unsigned int cpu, unsigned int state_count)
+static int psci_cpu_init_idle(struct device *dev, struct cpuidle_driver *drv,
+ unsigned int cpu, unsigned int state_count)
{
struct device_node *cpu_node;
int ret;
@@ -254,14 +234,22 @@ static __init int psci_cpu_init_idle(struct cpuidle_driver *drv,
if (!cpu_node)
return -ENODEV;
- ret = psci_dt_cpu_init_idle(drv, cpu_node, state_count, cpu);
+ ret = psci_dt_cpu_init_idle(dev, drv, cpu_node, state_count, cpu);
of_node_put(cpu_node);
return ret;
}
-static int __init psci_idle_init_cpu(int cpu)
+static void psci_cpu_deinit_idle(int cpu)
+{
+ struct psci_cpuidle_data *data = per_cpu_ptr(&psci_cpuidle_data, cpu);
+
+ psci_dt_detach_cpu(data->dev);
+ psci_cpuidle_use_cpuhp = false;
+}
+
+static int psci_idle_init_cpu(struct device *dev, int cpu)
{
struct cpuidle_driver *drv;
struct device_node *cpu_node;
@@ -284,17 +272,26 @@ static int __init psci_idle_init_cpu(int cpu)
if (ret)
return ret;
- drv = kmemdup(&psci_idle_driver, sizeof(*drv), GFP_KERNEL);
+ drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return -ENOMEM;
+ drv->name = "psci_idle";
+ drv->owner = THIS_MODULE;
drv->cpumask = (struct cpumask *)cpumask_of(cpu);
/*
- * Initialize idle states data, starting at index 1, since
- * by default idle state 0 is the quiescent state reached
- * by the cpu by executing the wfi instruction.
- *
+ * PSCI idle states relies on architectural WFI to be represented as
+ * state index 0.
+ */
+ drv->states[0].enter = psci_enter_idle_state;
+ drv->states[0].exit_latency = 1;
+ drv->states[0].target_residency = 1;
+ drv->states[0].power_usage = UINT_MAX;
+ strcpy(drv->states[0].name, "WFI");
+ strcpy(drv->states[0].desc, "ARM WFI");
+
+ /*
* If no DT idle states are detected (ret == 0) let the driver
* initialization fail accordingly since there is no reason to
* initialize the idle driver if only wfi is supported, the
@@ -302,48 +299,45 @@ static int __init psci_idle_init_cpu(int cpu)
* on idle entry.
*/
ret = dt_init_idle_driver(drv, psci_idle_state_match, 1);
- if (ret <= 0) {
- ret = ret ? : -ENODEV;
- goto out_kfree_drv;
- }
+ if (ret <= 0)
+ return ret ? : -ENODEV;
/*
* Initialize PSCI idle states.
*/
- ret = psci_cpu_init_idle(drv, cpu, ret);
+ ret = psci_cpu_init_idle(dev, drv, cpu, ret);
if (ret) {
pr_err("CPU %d failed to PSCI idle\n", cpu);
- goto out_kfree_drv;
+ return ret;
}
ret = cpuidle_register(drv, NULL);
if (ret)
- goto out_kfree_drv;
+ goto deinit;
cpuidle_cooling_register(drv);
return 0;
-
-out_kfree_drv:
- kfree(drv);
+deinit:
+ psci_cpu_deinit_idle(cpu);
return ret;
}
/*
- * psci_idle_init - Initializes PSCI cpuidle driver
+ * psci_idle_probe - Initializes PSCI cpuidle driver
*
* Initializes PSCI cpuidle driver for all CPUs, if any CPU fails
* to register cpuidle driver then rollback to cancel all CPUs
* registration.
*/
-static int __init psci_idle_init(void)
+static int psci_cpuidle_probe(struct platform_device *pdev)
{
int cpu, ret;
struct cpuidle_driver *drv;
struct cpuidle_device *dev;
for_each_possible_cpu(cpu) {
- ret = psci_idle_init_cpu(cpu);
+ ret = psci_idle_init_cpu(&pdev->dev, cpu);
if (ret)
goto out_fail;
}
@@ -356,9 +350,34 @@ out_fail:
dev = per_cpu(cpuidle_devices, cpu);
drv = cpuidle_get_cpu_driver(dev);
cpuidle_unregister(drv);
- kfree(drv);
+ psci_cpu_deinit_idle(cpu);
}
return ret;
}
+
+static struct platform_driver psci_cpuidle_driver = {
+ .probe = psci_cpuidle_probe,
+ .driver = {
+ .name = "psci-cpuidle",
+ },
+};
+
+static int __init psci_idle_init(void)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ ret = platform_driver_register(&psci_cpuidle_driver);
+ if (ret)
+ return ret;
+
+ pdev = platform_device_register_simple("psci-cpuidle", -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ platform_driver_unregister(&psci_cpuidle_driver);
+ return PTR_ERR(pdev);
+ }
+
+ return 0;
+}
device_initcall(psci_idle_init);
diff --git a/drivers/cpuidle/cpuidle-psci.h b/drivers/cpuidle/cpuidle-psci.h
index 7299a04dd467..d8e925e84c27 100644
--- a/drivers/cpuidle/cpuidle-psci.h
+++ b/drivers/cpuidle/cpuidle-psci.h
@@ -3,15 +3,18 @@
#ifndef __CPUIDLE_PSCI_H
#define __CPUIDLE_PSCI_H
+struct device;
struct device_node;
void psci_set_domain_state(u32 state);
-int __init psci_dt_parse_state_node(struct device_node *np, u32 *state);
+int psci_dt_parse_state_node(struct device_node *np, u32 *state);
-#ifdef CONFIG_PM_GENERIC_DOMAINS_OF
-struct device __init *psci_dt_attach_cpu(int cpu);
+#ifdef CONFIG_ARM_PSCI_CPUIDLE_DOMAIN
+struct device *psci_dt_attach_cpu(int cpu);
+void psci_dt_detach_cpu(struct device *dev);
#else
-static inline struct device __init *psci_dt_attach_cpu(int cpu) { return NULL; }
+static inline struct device *psci_dt_attach_cpu(int cpu) { return NULL; }
+static inline void psci_dt_detach_cpu(struct device *dev) { }
#endif
#endif /* __CPUIDLE_PSCI_H */
diff --git a/drivers/cpuidle/cpuidle-pseries.c b/drivers/cpuidle/cpuidle-pseries.c
index 6513ef2af66a..ff6d99e923a4 100644
--- a/drivers/cpuidle/cpuidle-pseries.c
+++ b/drivers/cpuidle/cpuidle-pseries.c
@@ -21,8 +21,9 @@
#include <asm/runlatch.h>
#include <asm/idle.h>
#include <asm/plpar_wrappers.h>
+#include <asm/rtas.h>
-struct cpuidle_driver pseries_idle_driver = {
+static struct cpuidle_driver pseries_idle_driver = {
.name = "pseries_idle",
.owner = THIS_MODULE,
};
@@ -86,19 +87,150 @@ static void check_and_cede_processor(void)
}
}
+/*
+ * XCEDE: Extended CEDE states discovered through the
+ * "ibm,get-systems-parameter" RTAS call with the token
+ * CEDE_LATENCY_TOKEN
+ */
+
+/*
+ * Section 7.3.16 System Parameters Option of PAPR version 2.8.1 has a
+ * table with all the parameters to ibm,get-system-parameters.
+ * CEDE_LATENCY_TOKEN corresponds to the token value for Cede Latency
+ * Settings Information.
+ */
+#define CEDE_LATENCY_TOKEN 45
+
+/*
+ * If the platform supports the cede latency settings information system
+ * parameter it must provide the following information in the NULL terminated
+ * parameter string:
+ *
+ * a. The first byte is the length ā€œNā€ of each cede latency setting record minus
+ * one (zero indicates a length of 1 byte).
+ *
+ * b. For each supported cede latency setting a cede latency setting record
+ * consisting of the first ā€œNā€ bytes as per the following table.
+ *
+ * -----------------------------
+ * | Field | Field |
+ * | Name | Length |
+ * -----------------------------
+ * | Cede Latency | 1 Byte |
+ * | Specifier Value | |
+ * -----------------------------
+ * | Maximum wakeup | |
+ * | latency in | 8 Bytes |
+ * | tb-ticks | |
+ * -----------------------------
+ * | Responsive to | |
+ * | external | 1 Byte |
+ * | interrupts | |
+ * -----------------------------
+ *
+ * This version has cede latency record size = 10.
+ *
+ * The structure xcede_latency_payload represents a) and b) with
+ * xcede_latency_record representing the table in b).
+ *
+ * xcede_latency_parameter is what gets returned by
+ * ibm,get-systems-parameter RTAS call when made with
+ * CEDE_LATENCY_TOKEN.
+ *
+ * These structures are only used to represent the data obtained by the RTAS
+ * call. The data is in big-endian.
+ */
+struct xcede_latency_record {
+ u8 hint;
+ __be64 latency_ticks;
+ u8 wake_on_irqs;
+} __packed;
+
+// Make space for 16 records, which "should be enough".
+struct xcede_latency_payload {
+ u8 record_size;
+ struct xcede_latency_record records[16];
+} __packed;
+
+struct xcede_latency_parameter {
+ __be16 payload_size;
+ struct xcede_latency_payload payload;
+ u8 null_char;
+} __packed;
+
+static unsigned int nr_xcede_records;
+static struct xcede_latency_parameter xcede_latency_parameter __initdata;
+
+static int __init parse_cede_parameters(void)
+{
+ struct xcede_latency_payload *payload;
+ u32 total_xcede_records_size;
+ u8 xcede_record_size;
+ u16 payload_size;
+ int ret, i;
+
+ ret = rtas_call(rtas_token("ibm,get-system-parameter"), 3, 1,
+ NULL, CEDE_LATENCY_TOKEN, __pa(&xcede_latency_parameter),
+ sizeof(xcede_latency_parameter));
+ if (ret) {
+ pr_err("xcede: Error parsing CEDE_LATENCY_TOKEN\n");
+ return ret;
+ }
+
+ payload_size = be16_to_cpu(xcede_latency_parameter.payload_size);
+ payload = &xcede_latency_parameter.payload;
+
+ xcede_record_size = payload->record_size + 1;
+
+ if (xcede_record_size != sizeof(struct xcede_latency_record)) {
+ pr_err("xcede: Expected record-size %lu. Observed size %u.\n",
+ sizeof(struct xcede_latency_record), xcede_record_size);
+ return -EINVAL;
+ }
+
+ pr_info("xcede: xcede_record_size = %d\n", xcede_record_size);
+
+ /*
+ * Since the payload_size includes the last NULL byte and the
+ * xcede_record_size, the remaining bytes correspond to array of all
+ * cede_latency settings.
+ */
+ total_xcede_records_size = payload_size - 2;
+ nr_xcede_records = total_xcede_records_size / xcede_record_size;
+
+ for (i = 0; i < nr_xcede_records; i++) {
+ struct xcede_latency_record *record = &payload->records[i];
+ u64 latency_ticks = be64_to_cpu(record->latency_ticks);
+ u8 wake_on_irqs = record->wake_on_irqs;
+ u8 hint = record->hint;
+
+ pr_info("xcede: Record %d : hint = %u, latency = 0x%llx tb ticks, Wake-on-irq = %u\n",
+ i, hint, latency_ticks, wake_on_irqs);
+ }
+
+ return 0;
+}
+
+#define NR_DEDICATED_STATES 2 /* snooze, CEDE */
+static u8 cede_latency_hint[NR_DEDICATED_STATES];
+
static int dedicated_cede_loop(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
+ u8 old_latency_hint;
pseries_idle_prolog();
get_lppaca()->donate_dedicated_cpu = 1;
+ old_latency_hint = get_lppaca()->cede_latency_hint;
+ get_lppaca()->cede_latency_hint = cede_latency_hint[index];
HMT_medium();
check_and_cede_processor();
local_irq_disable();
get_lppaca()->donate_dedicated_cpu = 0;
+ get_lppaca()->cede_latency_hint = old_latency_hint;
pseries_idle_epilog();
@@ -130,7 +262,7 @@ static int shared_cede_loop(struct cpuidle_device *dev,
/*
* States for dedicated partition case.
*/
-static struct cpuidle_state dedicated_states[] = {
+static struct cpuidle_state dedicated_states[NR_DEDICATED_STATES] = {
{ /* Snooze */
.name = "snooze",
.desc = "snooze",
@@ -211,6 +343,54 @@ static int pseries_cpuidle_driver_init(void)
return 0;
}
+static void __init fixup_cede0_latency(void)
+{
+ struct xcede_latency_payload *payload;
+ u64 min_latency_us;
+ int i;
+
+ min_latency_us = dedicated_states[1].exit_latency; // CEDE latency
+
+ if (parse_cede_parameters())
+ return;
+
+ pr_info("cpuidle: Skipping the %d Extended CEDE idle states\n",
+ nr_xcede_records);
+
+ payload = &xcede_latency_parameter.payload;
+ for (i = 0; i < nr_xcede_records; i++) {
+ struct xcede_latency_record *record = &payload->records[i];
+ u64 latency_tb = be64_to_cpu(record->latency_ticks);
+ u64 latency_us = tb_to_ns(latency_tb) / NSEC_PER_USEC;
+
+ if (latency_us < min_latency_us)
+ min_latency_us = latency_us;
+ }
+
+ /*
+ * By default, we assume that CEDE(0) has exit latency 10us,
+ * since there is no way for us to query from the platform.
+ *
+ * However, if the wakeup latency of an Extended CEDE state is
+ * smaller than 10us, then we can be sure that CEDE(0)
+ * requires no more than that.
+ *
+ * Perform the fix-up.
+ */
+ if (min_latency_us < dedicated_states[1].exit_latency) {
+ u64 cede0_latency = min_latency_us - 1;
+
+ if (cede0_latency <= 0)
+ cede0_latency = min_latency_us;
+
+ dedicated_states[1].exit_latency = cede0_latency;
+ dedicated_states[1].target_residency = 10 * (cede0_latency);
+ pr_info("cpuidle: Fixed up CEDE exit latency to %llu us\n",
+ cede0_latency);
+ }
+
+}
+
/*
* pseries_idle_probe()
* Choose state table for shared versus dedicated partition
@@ -232,8 +412,9 @@ static int pseries_idle_probe(void)
cpuidle_state_table = shared_states;
max_idle_state = ARRAY_SIZE(shared_states);
} else {
+ fixup_cede0_latency();
cpuidle_state_table = dedicated_states;
- max_idle_state = ARRAY_SIZE(dedicated_states);
+ max_idle_state = NR_DEDICATED_STATES;
}
} else
return -ENODEV;
diff --git a/drivers/cpuidle/cpuidle-tegra.c b/drivers/cpuidle/cpuidle-tegra.c
index 150045849d78..a12fb141875a 100644
--- a/drivers/cpuidle/cpuidle-tegra.c
+++ b/drivers/cpuidle/cpuidle-tegra.c
@@ -253,11 +253,13 @@ static int tegra_cpuidle_enter(struct cpuidle_device *dev,
return err ? -1 : index;
}
-static void tegra114_enter_s2idle(struct cpuidle_device *dev,
- struct cpuidle_driver *drv,
- int index)
+static int tegra114_enter_s2idle(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index)
{
tegra_cpuidle_enter(dev, drv, index);
+
+ return 0;
}
/*