linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] cpuidle: add freescale e500 family porcessors idle support
@ 2014-04-01  8:33 Dongsheng Wang
  2014-04-02  9:36 ` Daniel Lezcano
  2014-04-04 23:00 ` Scott Wood
  0 siblings, 2 replies; 15+ messages in thread
From: Dongsheng Wang @ 2014-04-01  8:33 UTC (permalink / raw)
  To: daniel.lezcano, scottwood
  Cc: chenhui.zhao, linux-pm, Wang Dongsheng, rjw, jason.jin,
	linuxppc-dev

From: Wang Dongsheng <dongsheng.wang@freescale.com>

Add cpuidle support for e500 family, using cpuidle framework to
manage various low power modes. The new implementation will remain
compatible with original idle method.

I have done test about power consumption and latency. Cpuidle framework
will make CPU response time faster than original method, but power
consumption is higher than original method.

Power consumption:
The original method, power consumption is 10.51202 (W).
The cpuidle framework, power consumption is 10.5311 (W).

Latency:
The original method, avg latency is 6782 (us).
The cpuidle framework, avg latency is 6482 (us).

Initially, this supports PW10, PW20 and subsequent patches will support
DOZE/NAP and PH10, PH20.

Signed-off-by: Wang Dongsheng <dongsheng.wang@freescale.com>

diff --git a/arch/powerpc/include/asm/machdep.h b/arch/powerpc/include/asm/machdep.h
index 5b6c03f..9301420 100644
--- a/arch/powerpc/include/asm/machdep.h
+++ b/arch/powerpc/include/asm/machdep.h
@@ -294,6 +294,15 @@ extern void power7_idle(void);
 extern void ppc6xx_idle(void);
 extern void book3e_idle(void);
 
+static inline void cpuidle_wait(void)
+{
+#ifdef CONFIG_PPC64
+	book3e_idle();
+#else
+	e500_idle();
+#endif
+}
+
 /*
  * ppc_md contains a copy of the machine description structure for the
  * current platform. machine_id contains the initial address where the
diff --git a/arch/powerpc/kernel/sysfs.c b/arch/powerpc/kernel/sysfs.c
index 97e1dc9..edd193f 100644
--- a/arch/powerpc/kernel/sysfs.c
+++ b/arch/powerpc/kernel/sysfs.c
@@ -190,6 +190,9 @@ static ssize_t show_pw20_wait_time(struct device *dev,
 	return sprintf(buf, "%llu\n", time > 0 ? time : 0);
 }
 
+#ifdef CONFIG_CPU_IDLE_E500
+u32 cpuidle_entry_bit;
+#endif
 static void set_pw20_wait_entry_bit(void *val)
 {
 	u32 *value = val;
@@ -204,7 +207,11 @@ static void set_pw20_wait_entry_bit(void *val)
 	/* set count */
 	pw20_idle |= ((MAX_BIT - *value) << PWRMGTCR0_PW20_ENT_SHIFT);
 
+#ifdef CONFIG_CPU_IDLE_E500
+	cpuidle_entry_bit = *value;
+#else
 	mtspr(SPRN_PWRMGTCR0, pw20_idle);
+#endif
 }
 
 static ssize_t store_pw20_wait_time(struct device *dev,
diff --git a/drivers/cpuidle/Kconfig.powerpc b/drivers/cpuidle/Kconfig.powerpc
index 66c3a09..0949dbf 100644
--- a/drivers/cpuidle/Kconfig.powerpc
+++ b/drivers/cpuidle/Kconfig.powerpc
@@ -18,3 +18,10 @@ config POWERNV_CPUIDLE
 	help
 	  Select this option to enable processor idle state management
 	  through cpuidle subsystem.
+
+config CPU_IDLE_E500
+	bool "CPU Idle Driver for E500 family processors"
+	depends on CPU_IDLE
+	depends on FSL_SOC_BOOKE
+	help
+	  Select this to enable cpuidle on e500 family processors.
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index f71ae1b..7e6adea 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_ARM_AT91_CPUIDLE)          += cpuidle-at91.o
 # POWERPC drivers
 obj-$(CONFIG_PSERIES_CPUIDLE)		+= cpuidle-pseries.o
 obj-$(CONFIG_POWERNV_CPUIDLE)		+= cpuidle-powernv.o
+obj-$(CONFIG_CPU_IDLE_E500)		+= cpuidle-e500.o
diff --git a/drivers/cpuidle/cpuidle-e500.c b/drivers/cpuidle/cpuidle-e500.c
new file mode 100644
index 0000000..ddc0def
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-e500.c
@@ -0,0 +1,194 @@
+/*
+ * CPU Idle driver for Freescale PowerPC e500 family processors.
+ *
+ * Copyright 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Dongsheng Wang <dongsheng.wang@freescale.com>
+ *
+ * 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.
+ */
+
+#include <linux/cpu.h>
+#include <linux/cpuidle.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/notifier.h>
+
+#include <asm/cputable.h>
+#include <asm/machdep.h>
+#include <asm/mpc85xx.h>
+
+static unsigned int max_idle_state;
+static struct cpuidle_state *cpuidle_state_table;
+
+struct cpuidle_driver e500_idle_driver = {
+	.name = "e500_idle",
+	.owner = THIS_MODULE,
+};
+
+static void e500_cpuidle(void)
+{
+	if (cpuidle_idle_call())
+		cpuidle_wait();
+}
+
+static int pw10_enter(struct cpuidle_device *dev,
+			struct cpuidle_driver *drv, int index)
+{
+	cpuidle_wait();
+	return index;
+}
+
+#define MAX_BIT	63
+#define MIN_BIT	1
+extern u32 cpuidle_entry_bit;
+static int pw20_enter(struct cpuidle_device *dev,
+		struct cpuidle_driver *drv, int index)
+{
+	u32 pw20_idle;
+	u32 entry_bit;
+	pw20_idle = mfspr(SPRN_PWRMGTCR0);
+	if ((pw20_idle & PWRMGTCR0_PW20_ENT) != PWRMGTCR0_PW20_ENT) {
+		pw20_idle &= ~PWRMGTCR0_PW20_ENT;
+		entry_bit = MAX_BIT - cpuidle_entry_bit;
+		pw20_idle |= (entry_bit << PWRMGTCR0_PW20_ENT_SHIFT);
+		mtspr(SPRN_PWRMGTCR0, pw20_idle);
+	}
+
+	cpuidle_wait();
+
+	pw20_idle &= ~PWRMGTCR0_PW20_ENT;
+	pw20_idle |= (MIN_BIT << PWRMGTCR0_PW20_ENT_SHIFT);
+	mtspr(SPRN_PWRMGTCR0, pw20_idle);
+
+	return index;
+}
+
+static struct cpuidle_state pw_idle_states[] = {
+	{
+		.name = "pw10",
+		.desc = "pw10",
+		.flags = CPUIDLE_FLAG_TIME_VALID,
+		.exit_latency = 0,
+		.target_residency = 0,
+		.enter = &pw10_enter
+	},
+
+	{
+		.name = "pw20",
+		.desc = "pw20-core-idle",
+		.flags = CPUIDLE_FLAG_TIME_VALID,
+		.exit_latency = 1,
+		.target_residency = 50,
+		.enter = &pw20_enter
+	},
+};
+
+static int cpu_hotplug_notify(struct notifier_block *n,
+			unsigned long action, void *hcpu)
+{
+	unsigned long hotcpu = (unsigned long)hcpu;
+	struct cpuidle_device *dev =
+			per_cpu_ptr(cpuidle_devices, hotcpu);
+
+	if (dev && cpuidle_get_driver()) {
+		switch (action) {
+		case CPU_ONLINE:
+		case CPU_ONLINE_FROZEN:
+			cpuidle_pause_and_lock();
+			cpuidle_enable_device(dev);
+			cpuidle_resume_and_unlock();
+			break;
+
+		case CPU_DEAD:
+		case CPU_DEAD_FROZEN:
+			cpuidle_pause_and_lock();
+			cpuidle_disable_device(dev);
+			cpuidle_resume_and_unlock();
+			break;
+
+		default:
+			return NOTIFY_DONE;
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block cpu_hotplug_notifier = {
+	.notifier_call = cpu_hotplug_notify,
+};
+
+static void e500_cpuidle_driver_init(void)
+{
+	int idle_state;
+	struct cpuidle_driver *drv = &e500_idle_driver;
+
+	drv->state_count = 0;
+
+	for (idle_state = 0; idle_state < max_idle_state; ++idle_state) {
+		if (!cpuidle_state_table[idle_state].enter)
+			break;
+
+		drv->states[drv->state_count] = cpuidle_state_table[idle_state];
+		drv->state_count++;
+	}
+}
+
+static int e500_idle_state_probe(void)
+{
+	if (cpuidle_disable != IDLE_NO_OVERRIDE)
+		return -ENODEV;
+
+	cpuidle_state_table = pw_idle_states;
+	max_idle_state = ARRAY_SIZE(pw_idle_states);
+
+	/* Disable PW20 feature for e500mc, e5500 */
+	if (PVR_VER(cur_cpu_spec->pvr_value) != PVR_VER_E6500)
+		cpuidle_state_table[1].enter = NULL;
+
+	if (!cpuidle_state_table || !max_idle_state)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void replace_orig_idle(void *dummy)
+{
+	return;
+}
+
+static int __init e500_idle_init(void)
+{
+	struct cpuidle_driver *drv = &e500_idle_driver;
+	int err;
+
+	if (e500_idle_state_probe())
+		return -ENODEV;
+
+	e500_cpuidle_driver_init();
+	if (!drv->state_count)
+		return -ENODEV;
+
+	err = cpuidle_register(drv, NULL);
+	if (err) {
+		pr_err("Register e500 family cpuidle driver failed.\n");
+
+		return err;
+	}
+
+	err = register_cpu_notifier(&cpu_hotplug_notifier);
+	if (err)
+		pr_warn("Cpuidle driver: register cpu notifier failed.\n");
+
+	/* Replace the original way of idle after cpuidle registered. */
+	ppc_md.power_save = e500_cpuidle;
+	on_each_cpu(replace_orig_idle, NULL, 1);
+
+	pr_info("e500_idle_driver registered.\n");
+
+	return 0;
+}
+late_initcall(e500_idle_init);
-- 
1.8.5

^ permalink raw reply related	[flat|nested] 15+ messages in thread
* [PATCH] cpuidle: add freescale e500 family porcessors idle support
@ 2013-07-30  7:00 Dongsheng Wang
  2013-07-30  9:51 ` Daniel Lezcano
  2013-07-30 19:38 ` Scott Wood
  0 siblings, 2 replies; 15+ messages in thread
From: Dongsheng Wang @ 2013-07-30  7:00 UTC (permalink / raw)
  To: scottwood, rjw, daniel.lezcano
  Cc: chenhui.zhao, linux-pm, Wang Dongsheng, linuxppc-dev

From: Wang Dongsheng <dongsheng.wang@freescale.com>

Add cpuidle support for e500 family, using cpuidle framework to
manage various low power modes. The new implementation will remain
compatible with original idle method.

Initially, this supports PW10, and subsequent patches will support
PW20/DOZE/NAP.

Signed-off-by: Wang Dongsheng <dongsheng.wang@freescale.com>
---
This patch keep using cpuidle_register_device(), because we need to support cpu
hotplug. I will fix "device" issue in this driver, after
Deepthi Dharwar <deepthi@linux.vnet.ibm.com> add a hotplug handler into cpuidle
freamwork.

diff --git a/arch/powerpc/include/asm/machdep.h b/arch/powerpc/include/asm/machdep.h
index 8b48090..cbdbe25 100644
--- a/arch/powerpc/include/asm/machdep.h
+++ b/arch/powerpc/include/asm/machdep.h
@@ -271,6 +271,16 @@ extern void power7_idle(void);
 extern void ppc6xx_idle(void);
 extern void book3e_idle(void);
 
+/* Wait for Interrupt */
+static inline void fsl_cpuidle_wait(void)
+{
+#ifdef CONFIG_PPC64
+	book3e_idle();
+#else
+	e500_idle();
+#endif
+}
+
 /*
  * ppc_md contains a copy of the machine description structure for the
  * current platform. machine_id contains the initial address where the
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index b3fb81d..7ed114b 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -35,6 +35,11 @@ depends on ARM
 source "drivers/cpuidle/Kconfig.arm"
 endmenu
 
+menu "PowerPC CPU Idle Drivers"
+depends on PPC32 || PPC64
+source "drivers/cpuidle/Kconfig.powerpc"
+endmenu
+
 endif
 
 config ARCH_NEEDS_CPU_IDLE_COUPLED
diff --git a/drivers/cpuidle/Kconfig.powerpc b/drivers/cpuidle/Kconfig.powerpc
new file mode 100644
index 0000000..9f3f5ef
--- /dev/null
+++ b/drivers/cpuidle/Kconfig.powerpc
@@ -0,0 +1,9 @@
+#
+# PowerPC CPU Idle drivers
+#
+
+config PPC_E500_CPUIDLE
+	bool "CPU Idle Driver for E500 family processors"
+	depends on FSL_SOC_BOOKE
+	help
+	  Select this to enable cpuidle on e500 family processors.
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 0b9d200..0dde3db 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -11,3 +11,7 @@ obj-$(CONFIG_ARM_HIGHBANK_CPUIDLE)	+= cpuidle-calxeda.o
 obj-$(CONFIG_ARM_KIRKWOOD_CPUIDLE)	+= cpuidle-kirkwood.o
 obj-$(CONFIG_ARM_ZYNQ_CPUIDLE)		+= cpuidle-zynq.o
 obj-$(CONFIG_ARM_U8500_CPUIDLE)         += cpuidle-ux500.o
+
+##################################################################################
+# PowerPC platform drivers
+obj-$(CONFIG_PPC_E500_CPUIDLE)		+= cpuidle-e500.o
diff --git a/drivers/cpuidle/cpuidle-e500.c b/drivers/cpuidle/cpuidle-e500.c
new file mode 100644
index 0000000..1919cea
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-e500.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2013 Freescale Semiconductor, Inc.
+ *
+ * CPU Idle driver for Freescale PowerPC e500 family processors.
+ *
+ * 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.
+ *
+ * Author: Wang Dongsheng <dongsheng.wang@freescale.com>
+ */
+
+#include <linux/cpu.h>
+#include <linux/cpuidle.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+
+#include <asm/machdep.h>
+
+static struct cpuidle_driver e500_idle_driver = {
+	.name = "e500_idle",
+	.owner = THIS_MODULE,
+};
+
+static struct cpuidle_device __percpu *e500_cpuidle_devices;
+
+static void e500_cpuidle(void)
+{
+	/*
+	 * This would call on the cpuidle framework, and the back-end
+	 * driver to go to idle states.
+	 */
+	if (cpuidle_idle_call()) {
+		/*
+		 * On error, execute default handler
+		 * to go into low thread priority and possibly
+		 * low power mode.
+		 */
+		HMT_low();
+		HMT_very_low();
+	}
+}
+
+static int pw10_enter(struct cpuidle_device *dev,
+			struct cpuidle_driver *drv, int index)
+{
+	fsl_cpuidle_wait();
+	return index;
+}
+
+static struct cpuidle_state fsl_pw_idle_states[] = {
+	{
+		.name = "pw10",
+		.desc = "pw10",
+		.flags = CPUIDLE_FLAG_TIME_VALID,
+		.exit_latency = 0,
+		.target_residency = 0,
+		.enter = &pw10_enter
+	},
+};
+
+static int cpu_hotplug_notify(struct notifier_block *n,
+			unsigned long action, void *hcpu)
+{
+	unsigned long hotcpu = (unsigned long)hcpu;
+	struct cpuidle_device *dev =
+			per_cpu_ptr(e500_cpuidle_devices, hotcpu);
+
+	if (dev && cpuidle_get_driver()) {
+		switch (action) {
+		case CPU_ONLINE:
+		case CPU_ONLINE_FROZEN:
+			cpuidle_pause_and_lock();
+			cpuidle_enable_device(dev);
+			cpuidle_resume_and_unlock();
+			break;
+
+		case CPU_DEAD:
+		case CPU_DEAD_FROZEN:
+			cpuidle_pause_and_lock();
+			cpuidle_disable_device(dev);
+			cpuidle_resume_and_unlock();
+			break;
+
+		default:
+			return NOTIFY_DONE;
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block cpu_hotplug_notifier = {
+	.notifier_call = cpu_hotplug_notify,
+};
+
+/*
+ * e500_idle_devices_init(void)
+ * allocate, initialize and register cpuidle device
+ */
+static int e500_idle_devices_init(void)
+{
+	int i;
+	struct cpuidle_driver *drv = &e500_idle_driver;
+	struct cpuidle_device *dev;
+
+	e500_cpuidle_devices = alloc_percpu(struct cpuidle_device);
+	if (!e500_cpuidle_devices)
+		return -ENOMEM;
+
+	for_each_possible_cpu(i) {
+		dev = per_cpu_ptr(e500_cpuidle_devices, i);
+		dev->state_count = drv->state_count;
+		dev->cpu = i;
+
+		if (cpuidle_register_device(dev)) {
+			pr_err("cpuidle_register_device %d failed!\n", i);
+			return -EIO;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * e500_idle_devices_uninit(void)
+ * unregister cpuidle devices and de-allocate memory
+ */
+static void e500_idle_devices_uninit(void)
+{
+	int i;
+	struct cpuidle_device *dev;
+
+	if (!e500_cpuidle_devices)
+		return;
+
+	for_each_possible_cpu(i) {
+		dev = per_cpu_ptr(e500_cpuidle_devices, i);
+		cpuidle_unregister_device(dev);
+	}
+
+	free_percpu(e500_cpuidle_devices);
+}
+
+static void e500_cpuidle_driver_init(unsigned int max_idle_state,
+		struct cpuidle_state *cpuidle_state_table)
+{
+	int idle_state;
+	struct cpuidle_driver *drv = &e500_idle_driver;
+
+	drv->state_count = 0;
+
+	for (idle_state = 0; idle_state < max_idle_state; ++idle_state) {
+		if (!cpuidle_state_table[idle_state].enter)
+			break;
+
+		drv->states[drv->state_count] = cpuidle_state_table[idle_state];
+		drv->state_count++;
+	}
+}
+
+static int cpu_is_feature(unsigned long feature)
+{
+	return (cur_cpu_spec->cpu_features == feature);
+}
+
+static int __init e500_idle_init(void)
+{
+	struct cpuidle_state *cpuidle_state_table = NULL;
+	struct cpuidle_driver *drv = &e500_idle_driver;
+	int err;
+	unsigned int max_idle_state = 0;
+
+	if (cpuidle_disable != IDLE_NO_OVERRIDE)
+		return -ENODEV;
+
+	if (cpu_is_feature(CPU_FTRS_E500MC) || cpu_is_feature(CPU_FTRS_E5500) ||
+			cpu_is_feature(CPU_FTRS_E6500)) {
+		cpuidle_state_table = fsl_pw_idle_states;
+		max_idle_state = ARRAY_SIZE(fsl_pw_idle_states);
+	}
+
+	if (!cpuidle_state_table || !max_idle_state)
+		return -EPERM;
+
+	e500_cpuidle_driver_init(max_idle_state, cpuidle_state_table);
+
+	if (!drv->state_count)
+		return -EPERM;
+
+	err = cpuidle_register_driver(drv);
+	if (err) {
+		pr_err("Register e500 family cpuidle driver failed.\n");
+
+		return err;
+	}
+
+	err = e500_idle_devices_init();
+	if (err)
+		goto out;
+
+	err = register_cpu_notifier(&cpu_hotplug_notifier);
+	if (err)
+		goto out;
+
+	ppc_md.power_save = e500_cpuidle;
+
+	pr_info("e500_idle_driver registered.\n");
+
+	return 0;
+
+out:
+	e500_idle_devices_uninit();
+	cpuidle_unregister_driver(drv);
+
+	pr_err("Register e500 family cpuidle driver failed.\n");
+
+	return err;
+}
+device_initcall(e500_idle_init);
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2014-04-04 23:00 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-04-01  8:33 [PATCH] cpuidle: add freescale e500 family porcessors idle support Dongsheng Wang
2014-04-02  9:36 ` Daniel Lezcano
2014-04-02  9:39   ` Daniel Lezcano
2014-04-03  3:20   ` Dongsheng.Wang
2014-04-03  6:28     ` Daniel Lezcano
2014-04-03  8:03       ` Dongsheng.Wang
2014-04-03  8:12         ` Daniel Lezcano
2014-04-04 23:00 ` Scott Wood
  -- strict thread matches above, loose matches on Subject: below --
2013-07-30  7:00 Dongsheng Wang
2013-07-30  9:51 ` Daniel Lezcano
2013-07-30 11:02   ` Wang Dongsheng-B40534
2013-07-31  3:03     ` Deepthi Dharwar
2013-07-30 19:38 ` Scott Wood
2013-07-31  6:30   ` Wang Dongsheng-B40534
2013-08-01  0:23     ` Scott Wood

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).