[PATCH v2] Add cpuidle support for at91

Nicolas Ferre nicolas.ferre at atmel.com
Tue Sep 29 10:15:39 EDT 2009


Salut Albin !

Albin Tonnerre :
> Nicolas, Andrew: ping?

Indeed it's been a long time...

> On Thu, 13 Aug 2009 21:32 +0200, Albin Tonnerre wrote :
>> This patch adds the support for cpuidle on AT91 SoCs, taken from the
>> cpuidle support in mach-kirkwood.
>> cpuidle needs sdram_selfrefresh_enable and _disable, so move their
>> definition to a separate header file instead of duplicating the code
>> already used in pm.c.
>>
>> Signed-off-by: Albin Tonnerre <albin.tonnerre at free-electrons.com>

[nicolas.ferre at atmel.com: tested on at91sam9263ek]
Tested-by: Nicolas Ferre <nicolas.ferre at atmel.com>

Works very well and allow to save some power. After quick measures, we
run at 172mA at 12V instead of 190mA at 12V so ~2.06W and a saving of ~10% :
nice job !
This is of course in my test conditions.

Just one precision: what is the difference, entering state0 with only
the current cpu_do_idle() that is activated by default ?

>> ---
>> Changelog since V1:
>> Updated thanks to feedback from Marc Pignat:
>>  - On AT91RM200, it is necessary to make sure that the Low-power mode is
>>    left before entering the self-refresh mode
>>  - Document the fact that restoring the low-power mode when we're not
>>    sure we're out of self-refresh mode is not recommended on RM200
>>
>>  arch/arm/mach-at91/Makefile  |    1 +
>>  arch/arm/mach-at91/cpuidle.c |  101 ++++++++++++++++++++++++++++++++++++++++++
>>  arch/arm/mach-at91/pm.c      |   62 ++------------------------
>>  arch/arm/mach-at91/pm.h      |   62 ++++++++++++++++++++++++++
>>  4 files changed, 168 insertions(+), 58 deletions(-)
>>  mode change 100644 => 100755 arch/arm/mach-at91/Makefile
>>  create mode 100644 arch/arm/mach-at91/cpuidle.c
>>  create mode 100644 arch/arm/mach-at91/pm.h
>>
>> diff --git a/arch/arm/mach-at91/Makefile b/arch/arm/mach-at91/Makefile
>> old mode 100644
>> new mode 100755
>> index c69ff23..06d189d
>> --- a/arch/arm/mach-at91/Makefile
>> +++ b/arch/arm/mach-at91/Makefile
>> @@ -67,6 +67,7 @@ obj-y				+= leds.o
>>  # Power Management
>>  obj-$(CONFIG_PM)		+= pm.o
>>  obj-$(CONFIG_AT91_SLOW_CLOCK)	+= pm_slowclock.o
>> +obj-$(CONFIG_CPU_IDLE)	+= cpuidle.o
>>  
>>  ifeq ($(CONFIG_PM_DEBUG),y)
>>  CFLAGS_pm.o += -DDEBUG
>> diff --git a/arch/arm/mach-at91/cpuidle.c b/arch/arm/mach-at91/cpuidle.c
>> new file mode 100644
>> index 0000000..9146135
>> --- /dev/null
>> +++ b/arch/arm/mach-at91/cpuidle.c
>> @@ -0,0 +1,101 @@
>> +/*
>> + * based on arch/arm/mach-kirkwood/cpuidle.c
>> + *
>> + * CPU idle support for AT91 SoC
>> + *
>> + * This file is licensed under the terms of the GNU General Public
>> + * License version 2.  This program is licensed "as is" without any
>> + * warranty of any kind, whether express or implied.
>> + *
>> + * The cpu idle uses wait-for-interrupt and RAM self refresh in order
>> + * to implement two idle states -
>> + * #1 wait-for-interrupt
>> + * #2 wait-for-interrupt and RAM self refresh
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/init.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/cpuidle.h>
>> +#include <asm/proc-fns.h>
>> +#include <linux/io.h>
>> +
>> +#include "pm.h"
>> +
>> +#define AT91_MAX_STATES	2
>> +
>> +static DEFINE_PER_CPU(struct cpuidle_device, at91_cpuidle_device);
>> +
>> +static struct cpuidle_driver at91_idle_driver = {
>> +	.name =         "at91_idle",
>> +	.owner =        THIS_MODULE,
>> +};
>> +
>> +/* Actual code that puts the SoC in different idle states */
>> +static int at91_enter_idle(struct cpuidle_device *dev,
>> +			       struct cpuidle_state *state)
>> +{
>> +	struct timeval before, after;
>> +	int idle_time;
>> +	u32 saved_lpr;
>> +
>> +	local_irq_disable();
>> +	do_gettimeofday(&before);
>> +	if (state == &dev->states[0])
>> +		/* Wait for interrupt state */
>> +		cpu_do_idle();
>> +	else if (state == &dev->states[1]) {
>> +		asm("b 1f; .align 5; 1:");
>> +		asm("mcr p15, 0, r0, c7, c10, 4");	/* drain write buffer */
>> +		saved_lpr = sdram_selfrefresh_enable();
>> +		cpu_do_idle();
>> +		/*
>> +		 * On AT91RM200, self-refresh mode is exited as soon as a memory access
>> +		 * is made, but we don't know for sure when that happens. However, we
>> +		 * need to restore the low-power mode if it was enabled before going
>> +		 * idle. Restoring low-power mode while still in self-refresh is "not
>> +		 * recommended", but seems to work.
>> +		 */
>> +		sdram_selfrefresh_disable(saved_lpr);
>> +	}
>> +	do_gettimeofday(&after);
>> +	local_irq_enable();
>> +	idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
>> +			(after.tv_usec - before.tv_usec);
>> +	return idle_time;
>> +}
>> +
>> +/* Initialize CPU idle by registering the idle states */
>> +static int at91_init_cpuidle(void)
>> +{
>> +	struct cpuidle_device *device;
>> +
>> +	cpuidle_register_driver(&at91_idle_driver);
>> +
>> +	device = &per_cpu(at91_cpuidle_device, smp_processor_id());
>> +	device->state_count = AT91_MAX_STATES;
>> +
>> +	/* Wait for interrupt state */
>> +	device->states[0].enter = at91_enter_idle;
>> +	device->states[0].exit_latency = 1;
>> +	device->states[0].target_residency = 10000;
>> +	device->states[0].flags = CPUIDLE_FLAG_TIME_VALID;
>> +	strcpy(device->states[0].name, "WFI");
>> +	strcpy(device->states[0].desc, "Wait for interrupt");
>> +
>> +	/* Wait for interrupt and RAM self refresh state */
>> +	device->states[1].enter = at91_enter_idle;
>> +	device->states[1].exit_latency = 10;
>> +	device->states[1].target_residency = 10000;
>> +	device->states[1].flags = CPUIDLE_FLAG_TIME_VALID;
>> +	strcpy(device->states[1].name, "RAM_SR");
>> +	strcpy(device->states[1].desc, "WFI and RAM Self Refresh");
>> +
>> +	if (cpuidle_register_device(device)) {
>> +		printk(KERN_ERR "at91_init_cpuidle: Failed registering\n");
>> +		return -EIO;
>> +	}
>> +	return 0;
>> +}
>> +
>> +device_initcall(at91_init_cpuidle);
>> diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c
>> index e26c4fe..0a68c46 100644
>> --- a/arch/arm/mach-at91/pm.c
>> +++ b/arch/arm/mach-at91/pm.c
>> @@ -29,62 +29,7 @@
>>  #include <mach/cpu.h>
>>  
>>  #include "generic.h"
>> -
>> -#ifdef CONFIG_ARCH_AT91RM9200
>> -#include <mach/at91rm9200_mc.h>
>> -
>> -/*
>> - * The AT91RM9200 goes into self-refresh mode with this command, and will
>> - * terminate self-refresh automatically on the next SDRAM access.
>> - */
>> -#define sdram_selfrefresh_enable()	at91_sys_write(AT91_SDRAMC_SRR, 1)
>> -#define sdram_selfrefresh_disable()	do {} while (0)
>> -
>> -#elif defined(CONFIG_ARCH_AT91CAP9)
>> -#include <mach/at91cap9_ddrsdr.h>
>> -
>> -static u32 saved_lpr;
>> -
>> -static inline void sdram_selfrefresh_enable(void)
>> -{
>> -	u32 lpr;
>> -
>> -	saved_lpr = at91_sys_read(AT91_DDRSDRC_LPR);
>> -
>> -	lpr = saved_lpr & ~AT91_DDRSDRC_LPCB;
>> -	at91_sys_write(AT91_DDRSDRC_LPR, lpr | AT91_DDRSDRC_LPCB_SELF_REFRESH);
>> -}
>> -
>> -#define sdram_selfrefresh_disable()	at91_sys_write(AT91_DDRSDRC_LPR, saved_lpr)
>> -
>> -#else
>> -#include <mach/at91sam9_sdramc.h>
>> -
>> -#ifdef CONFIG_ARCH_AT91SAM9263
>> -/*
>> - * FIXME either or both the SDRAM controllers (EB0, EB1) might be in use;
>> - * handle those cases both here and in the Suspend-To-RAM support.
>> - */
>> -#define	AT91_SDRAMC	AT91_SDRAMC0
>> -#warning Assuming EB1 SDRAM controller is *NOT* used
>> -#endif
>> -
>> -static u32 saved_lpr;
>> -
>> -static inline void sdram_selfrefresh_enable(void)
>> -{
>> -	u32 lpr;
>> -
>> -	saved_lpr = at91_sys_read(AT91_SDRAMC_LPR);
>> -
>> -	lpr = saved_lpr & ~AT91_SDRAMC_LPCB;
>> -	at91_sys_write(AT91_SDRAMC_LPR, lpr | AT91_SDRAMC_LPCB_SELF_REFRESH);
>> -}
>> -
>> -#define sdram_selfrefresh_disable()	at91_sys_write(AT91_SDRAMC_LPR, saved_lpr)
>> -
>> -#endif
>> -
>> +#include "pm.h"
>>  
>>  /*
>>   * Show the reason for the previous system reset.
>> @@ -259,6 +204,7 @@ extern u32 at91_slow_clock_sz;
>>  
>>  static int at91_pm_enter(suspend_state_t state)
>>  {
>> +	u32 saved_lpr;
>>  	at91_gpio_suspend();
>>  	at91_irq_suspend();
>>  
>> @@ -314,9 +260,9 @@ static int at91_pm_enter(suspend_state_t state)
>>  			 */
>>  			asm("b 1f; .align 5; 1:");
>>  			asm("mcr p15, 0, r0, c7, c10, 4");	/* drain write buffer */
>> -			sdram_selfrefresh_enable();
>> +			saved_lpr = sdram_selfrefresh_enable();
>>  			asm("mcr p15, 0, r0, c7, c0, 4");	/* wait for interrupt */
>> -			sdram_selfrefresh_disable();
>> +			sdram_selfrefresh_disable(saved_lpr);
>>  			break;
>>  
>>  		case PM_SUSPEND_ON:
>> diff --git a/arch/arm/mach-at91/pm.h b/arch/arm/mach-at91/pm.h
>> new file mode 100644
>> index 0000000..0b8b717
>> --- /dev/null
>> +++ b/arch/arm/mach-at91/pm.h
>> @@ -0,0 +1,62 @@
>> +#ifdef CONFIG_ARCH_AT91RM9200
>> +#include <mach/at91rm9200_mc.h>
>> +
>> +/*
>> + * The AT91RM9200 goes into self-refresh mode with this command, and will
>> + * terminate self-refresh automatically on the next SDRAM access.
>> + */
>> +
>> +static inline u32 sdram_selfrefresh_enable(void)
>> +{
>> +	u32 saved_lpr = at91_sys_read(AT91_SDRAMC_LPR);
>> +
>> +	at91_sys_write(AT91_SDRAMC_LPR, 0);
>> +	at91_sys_write(AT91_SDRAMC_SRR, 1);
>> +	return saved_lpr;
>> +}
>> +
>> +#define sdram_selfrefresh_disable(saved_lpr)	do { at91_sys_write(AT91_SDRAMC_LPR, saved_lpr); } while (0)
>> +
>> +#elif defined(CONFIG_ARCH_AT91CAP9)
>> +#include <mach/at91cap9_ddrsdr.h>
>> +
>> +
>> +static inline u32 sdram_selfrefresh_enable(void)
>> +{
>> +	u32 saved_lpr, lpr;
>> +
>> +	saved_lpr = at91_sys_read(AT91_DDRSDRC_LPR);
>> +
>> +	lpr = saved_lpr & ~AT91_DDRSDRC_LPCB;
>> +	at91_sys_write(AT91_DDRSDRC_LPR, lpr | AT91_DDRSDRC_LPCB_SELF_REFRESH);
>> +	return saved_lpr;
>> +}
>> +
>> +#define sdram_selfrefresh_disable(saved_lpr)	at91_sys_write(AT91_DDRSDRC_LPR, saved_lpr)
>> +
>> +#else
>> +#include <mach/at91sam9_sdramc.h>
>> +
>> +#ifdef CONFIG_ARCH_AT91SAM9263
>> +/*
>> + * FIXME either or both the SDRAM controllers (EB0, EB1) might be in use;
>> + * handle those cases both here and in the Suspend-To-RAM support.
>> + */
>> +#define	AT91_SDRAMC	AT91_SDRAMC0
>> +#warning Assuming EB1 SDRAM controller is *NOT* used
>> +#endif
>> +
>> +static inline u32 sdram_selfrefresh_enable(void)
>> +{
>> +	u32 saved_lpr, lpr;
>> +
>> +	saved_lpr = at91_sys_read(AT91_SDRAMC_LPR);
>> +
>> +	lpr = saved_lpr & ~AT91_SDRAMC_LPCB;
>> +	at91_sys_write(AT91_SDRAMC_LPR, lpr | AT91_SDRAMC_LPCB_SELF_REFRESH);
>> +	return saved_lpr;
>> +}
>> +
>> +#define sdram_selfrefresh_disable(saved_lpr)	at91_sys_write(AT91_SDRAMC_LPR, saved_lpr)
>> +
>> +#endif


-- 
Nicolas Ferre






More information about the linux-arm-kernel mailing list