diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-01-06 17:58:22 -0800 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-01-06 17:58:22 -0800 | 
| commit | e4e88f31bcb5f05f24b9ae518d4ecb44e1a7774d (patch) | |
| tree | 9eef6998f5bbd1a2c999011d9e0151f00c6e7297 /arch/powerpc/platforms/pseries/processor_idle.c | |
| parent | 9753dfe19a85e7e45a34a56f4cb2048bb4f50e27 (diff) | |
| parent | ef88e3911c0e0301e73fa3b3b2567aabdbe17cc4 (diff) | |
| download | olio-linux-3.10-e4e88f31bcb5f05f24b9ae518d4ecb44e1a7774d.tar.xz olio-linux-3.10-e4e88f31bcb5f05f24b9ae518d4ecb44e1a7774d.zip  | |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc: (185 commits)
  powerpc: fix compile error with 85xx/p1010rdb.c
  powerpc: fix compile error with 85xx/p1023_rds.c
  powerpc/fsl: add MSI support for the Freescale hypervisor
  arch/powerpc/sysdev/fsl_rmu.c: introduce missing kfree
  powerpc/fsl: Add support for Integrated Flash Controller
  powerpc/fsl: update compatiable on fsl 16550 uart nodes
  powerpc/85xx: fix PCI and localbus properties in p1022ds.dts
  powerpc/85xx: re-enable ePAPR byte channel driver in corenet32_smp_defconfig
  powerpc/fsl: Update defconfigs to enable some standard FSL HW features
  powerpc: Add TBI PHY node to first MDIO bus
  sbc834x: put full compat string in board match check
  powerpc/fsl-pci: Allow 64-bit PCIe devices to DMA to any memory address
  powerpc: Fix unpaired probe_hcall_entry and probe_hcall_exit
  offb: Fix setting of the pseudo-palette for >8bpp
  offb: Add palette hack for qemu "standard vga" framebuffer
  offb: Fix bug in calculating requested vram size
  powerpc/boot: Change the WARN to INFO for boot wrapper overlap message
  powerpc/44x: Fix build error on currituck platform
  powerpc/boot: Change the load address for the wrapper to fit the kernel
  powerpc/44x: Enable CRASH_DUMP for 440x
  ...
Fix up a trivial conflict in arch/powerpc/include/asm/cputime.h due to
the additional sparse-checking code for cputime_t.
Diffstat (limited to 'arch/powerpc/platforms/pseries/processor_idle.c')
| -rw-r--r-- | arch/powerpc/platforms/pseries/processor_idle.c | 329 | 
1 files changed, 329 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/pseries/processor_idle.c b/arch/powerpc/platforms/pseries/processor_idle.c new file mode 100644 index 00000000000..085fd3f45ad --- /dev/null +++ b/arch/powerpc/platforms/pseries/processor_idle.c @@ -0,0 +1,329 @@ +/* + *  processor_idle - idle state cpuidle driver. + *  Adapted from drivers/idle/intel_idle.c and + *  drivers/acpi/processor_idle.c + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/cpuidle.h> +#include <linux/cpu.h> + +#include <asm/paca.h> +#include <asm/reg.h> +#include <asm/system.h> +#include <asm/machdep.h> +#include <asm/firmware.h> + +#include "plpar_wrappers.h" +#include "pseries.h" + +struct cpuidle_driver pseries_idle_driver = { +	.name =		"pseries_idle", +	.owner =	THIS_MODULE, +}; + +#define MAX_IDLE_STATE_COUNT	2 + +static int max_idle_state = MAX_IDLE_STATE_COUNT - 1; +static struct cpuidle_device __percpu *pseries_cpuidle_devices; +static struct cpuidle_state *cpuidle_state_table; + +void update_smt_snooze_delay(int snooze) +{ +	struct cpuidle_driver *drv = cpuidle_get_driver(); +	if (drv) +		drv->states[0].target_residency = snooze; +} + +static inline void idle_loop_prolog(unsigned long *in_purr, ktime_t *kt_before) +{ + +	*kt_before = ktime_get_real(); +	*in_purr = mfspr(SPRN_PURR); +	/* +	 * Indicate to the HV that we are idle. Now would be +	 * a good time to find other work to dispatch. +	 */ +	get_lppaca()->idle = 1; +} + +static inline  s64 idle_loop_epilog(unsigned long in_purr, ktime_t kt_before) +{ +	get_lppaca()->wait_state_cycles += mfspr(SPRN_PURR) - in_purr; +	get_lppaca()->idle = 0; + +	return ktime_to_us(ktime_sub(ktime_get_real(), kt_before)); +} + +static int snooze_loop(struct cpuidle_device *dev, +			struct cpuidle_driver *drv, +			int index) +{ +	unsigned long in_purr; +	ktime_t kt_before; +	unsigned long start_snooze; +	long snooze = drv->states[0].target_residency; + +	idle_loop_prolog(&in_purr, &kt_before); + +	if (snooze) { +		start_snooze = get_tb() + snooze * tb_ticks_per_usec; +		local_irq_enable(); +		set_thread_flag(TIF_POLLING_NRFLAG); + +		while ((snooze < 0) || (get_tb() < start_snooze)) { +			if (need_resched() || cpu_is_offline(dev->cpu)) +				goto out; +			ppc64_runlatch_off(); +			HMT_low(); +			HMT_very_low(); +		} + +		HMT_medium(); +		clear_thread_flag(TIF_POLLING_NRFLAG); +		smp_mb(); +		local_irq_disable(); +	} + +out: +	HMT_medium(); +	dev->last_residency = +		(int)idle_loop_epilog(in_purr, kt_before); +	return index; +} + +static int dedicated_cede_loop(struct cpuidle_device *dev, +				struct cpuidle_driver *drv, +				int index) +{ +	unsigned long in_purr; +	ktime_t kt_before; + +	idle_loop_prolog(&in_purr, &kt_before); +	get_lppaca()->donate_dedicated_cpu = 1; + +	ppc64_runlatch_off(); +	HMT_medium(); +	cede_processor(); + +	get_lppaca()->donate_dedicated_cpu = 0; +	dev->last_residency = +		(int)idle_loop_epilog(in_purr, kt_before); +	return index; +} + +static int shared_cede_loop(struct cpuidle_device *dev, +			struct cpuidle_driver *drv, +			int index) +{ +	unsigned long in_purr; +	ktime_t kt_before; + +	idle_loop_prolog(&in_purr, &kt_before); + +	/* +	 * Yield the processor to the hypervisor.  We return if +	 * an external interrupt occurs (which are driven prior +	 * to returning here) or if a prod occurs from another +	 * processor. When returning here, external interrupts +	 * are enabled. +	 */ +	cede_processor(); + +	dev->last_residency = +		(int)idle_loop_epilog(in_purr, kt_before); +	return index; +} + +/* + * States for dedicated partition case. + */ +static struct cpuidle_state dedicated_states[MAX_IDLE_STATE_COUNT] = { +	{ /* Snooze */ +		.name = "snooze", +		.desc = "snooze", +		.flags = CPUIDLE_FLAG_TIME_VALID, +		.exit_latency = 0, +		.target_residency = 0, +		.enter = &snooze_loop }, +	{ /* CEDE */ +		.name = "CEDE", +		.desc = "CEDE", +		.flags = CPUIDLE_FLAG_TIME_VALID, +		.exit_latency = 1, +		.target_residency = 10, +		.enter = &dedicated_cede_loop }, +}; + +/* + * States for shared partition case. + */ +static struct cpuidle_state shared_states[MAX_IDLE_STATE_COUNT] = { +	{ /* Shared Cede */ +		.name = "Shared Cede", +		.desc = "Shared Cede", +		.flags = CPUIDLE_FLAG_TIME_VALID, +		.exit_latency = 0, +		.target_residency = 0, +		.enter = &shared_cede_loop }, +}; + +int pseries_notify_cpuidle_add_cpu(int cpu) +{ +	struct cpuidle_device *dev = +			per_cpu_ptr(pseries_cpuidle_devices, cpu); +	if (dev && cpuidle_get_driver()) { +		cpuidle_disable_device(dev); +		cpuidle_enable_device(dev); +	} +	return 0; +} + +/* + * pseries_cpuidle_driver_init() + */ +static int pseries_cpuidle_driver_init(void) +{ +	int idle_state; +	struct cpuidle_driver *drv = &pseries_idle_driver; + +	drv->state_count = 0; + +	for (idle_state = 0; idle_state < MAX_IDLE_STATE_COUNT; ++idle_state) { + +		if (idle_state > max_idle_state) +			break; + +		/* is the state not enabled? */ +		if (cpuidle_state_table[idle_state].enter == NULL) +			continue; + +		drv->states[drv->state_count] =	/* structure copy */ +			cpuidle_state_table[idle_state]; + +		if (cpuidle_state_table == dedicated_states) +			drv->states[drv->state_count].target_residency = +				__get_cpu_var(smt_snooze_delay); + +		drv->state_count += 1; +	} + +	return 0; +} + +/* pseries_idle_devices_uninit(void) + * unregister cpuidle devices and de-allocate memory + */ +static void pseries_idle_devices_uninit(void) +{ +	int i; +	struct cpuidle_device *dev; + +	for_each_possible_cpu(i) { +		dev = per_cpu_ptr(pseries_cpuidle_devices, i); +		cpuidle_unregister_device(dev); +	} + +	free_percpu(pseries_cpuidle_devices); +	return; +} + +/* pseries_idle_devices_init() + * allocate, initialize and register cpuidle device + */ +static int pseries_idle_devices_init(void) +{ +	int i; +	struct cpuidle_driver *drv = &pseries_idle_driver; +	struct cpuidle_device *dev; + +	pseries_cpuidle_devices = alloc_percpu(struct cpuidle_device); +	if (pseries_cpuidle_devices == NULL) +		return -ENOMEM; + +	for_each_possible_cpu(i) { +		dev = per_cpu_ptr(pseries_cpuidle_devices, i); +		dev->state_count = drv->state_count; +		dev->cpu = i; +		if (cpuidle_register_device(dev)) { +			printk(KERN_DEBUG \ +				"cpuidle_register_device %d failed!\n", i); +			return -EIO; +		} +	} + +	return 0; +} + +/* + * pseries_idle_probe() + * Choose state table for shared versus dedicated partition + */ +static int pseries_idle_probe(void) +{ + +	if (!firmware_has_feature(FW_FEATURE_SPLPAR)) +		return -ENODEV; + +	if (cpuidle_disable != IDLE_NO_OVERRIDE) +		return -ENODEV; + +	if (max_idle_state == 0) { +		printk(KERN_DEBUG "pseries processor idle disabled.\n"); +		return -EPERM; +	} + +	if (get_lppaca()->shared_proc) +		cpuidle_state_table = shared_states; +	else +		cpuidle_state_table = dedicated_states; + +	return 0; +} + +static int __init pseries_processor_idle_init(void) +{ +	int retval; + +	retval = pseries_idle_probe(); +	if (retval) +		return retval; + +	pseries_cpuidle_driver_init(); +	retval = cpuidle_register_driver(&pseries_idle_driver); +	if (retval) { +		printk(KERN_DEBUG "Registration of pseries driver failed.\n"); +		return retval; +	} + +	retval = pseries_idle_devices_init(); +	if (retval) { +		pseries_idle_devices_uninit(); +		cpuidle_unregister_driver(&pseries_idle_driver); +		return retval; +	} + +	printk(KERN_DEBUG "pseries_idle_driver registered\n"); + +	return 0; +} + +static void __exit pseries_processor_idle_exit(void) +{ + +	pseries_idle_devices_uninit(); +	cpuidle_unregister_driver(&pseries_idle_driver); + +	return; +} + +module_init(pseries_processor_idle_init); +module_exit(pseries_processor_idle_exit); + +MODULE_AUTHOR("Deepthi Dharwar <deepthi@linux.vnet.ibm.com>"); +MODULE_DESCRIPTION("Cpuidle driver for POWER"); +MODULE_LICENSE("GPL");  |