diff mbox

[2/2] fsl/mpic_timer: make mpic_timer to support deep sleep feature

Message ID 1397442250-14886-2-git-send-email-dongsheng.wang@freescale.com (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Dongsheng Wang April 14, 2014, 2:24 a.m. UTC
From: Wang Dongsheng <dongsheng.wang@freescale.com>

At T104x platfrom the timer clock will be changed when system going to
deep sleep. Add suspend function to switch timer time before system
going to deep sleep, and recovery the time after resume from deep sleep.

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

Comments

Scott Wood April 14, 2014, 11:35 p.m. UTC | #1
On Mon, 2014-04-14 at 10:24 +0800, Dongsheng Wang wrote:
> From: Wang Dongsheng <dongsheng.wang@freescale.com>
> 
> At T104x platfrom the timer clock will be changed when system going to
> deep sleep.

Could you elaborate on what is changing and why?

> +#include <asm/mpc85xx.h>
>  #include <asm/mpic_timer.h>

So much for, "The driver currently is only tested on fsl chip, but it
can potentially support other global timers complying to OpenPIC
standard."

>  #define FSL_GLOBAL_TIMER		0x1
> @@ -71,8 +74,10 @@ struct timer_group_priv {
>  	struct timer_regs __iomem	*regs;
>  	struct mpic_timer		timer[TIMERS_PER_GROUP];
>  	struct list_head		node;
> +	unsigned long			idle;
>  	unsigned int			timerfreq;
> -	unsigned int			idle;

Why?

> +	unsigned int			suspended_timerfreq;
> +	unsigned int			resume_timerfreq;
>  	unsigned int			flags;
>  	spinlock_t			lock;
>  	void __iomem			*group_tcr;
> @@ -88,6 +93,7 @@ static struct cascade_priv cascade_timer[] = {
>  };
>  
>  static LIST_HEAD(timer_group_list);
> +static int switch_freq_flag;

Needs documentation, and based on "_flag" it should probably be a bool.

>  static void convert_ticks_to_time(struct timer_group_priv *priv,
>  		const u64 ticks, struct timeval *time)
> @@ -423,6 +429,33 @@ struct mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev,
>  }
>  EXPORT_SYMBOL(mpic_request_timer);
>  
> +static void timer_group_get_suspended_freq(struct timer_group_priv *priv)
> +{
> +	struct device_node *np;
> +
> +	np = of_find_compatible_node(NULL, NULL, "fsl,qoriq-clockgen-2.0");
> +	if (!np) {
> +		pr_err("mpic timer: Missing clockgen device node.\n");

Why is it an error to not have a 2.0 QorIQ clockgen?

> +		return;
> +	}
> +
> +	of_property_read_u32(np, "clock-frequency", &priv->suspended_timerfreq);
> +	of_node_put(np);

Shouldn't this go through the clock API?

> +}
> +
> +static int need_to_switch_freq(void)
> +{
> +	u32 svr;
> +
> +	svr = mfspr(SPRN_SVR);
> +	if (SVR_SOC_VER(svr) == SVR_T1040 ||
> +			SVR_SOC_VER(svr) == SVR_T1042)
> +		return 1;

Explain why this is specific to T104x.

-Scott
Dongsheng Wang April 15, 2014, 3:23 a.m. UTC | #2
> -----Original Message-----

> From: Wood Scott-B07421

> Sent: Tuesday, April 15, 2014 7:36 AM

> To: Wang Dongsheng-B40534

> Cc: Jin Zhengxiong-R64188; Li Yang-Leo-R58472; Zhao Chenhui-B35336; linuxppc-

> dev@lists.ozlabs.org

> Subject: Re: [PATCH 2/2] fsl/mpic_timer: make mpic_timer to support deep sleep

> feature

> 

> On Mon, 2014-04-14 at 10:24 +0800, Dongsheng Wang wrote:

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

> >

> > At T104x platfrom the timer clock will be changed when system going to

> > deep sleep.

> 

> Could you elaborate on what is changing and why?

> 


Okay.

> > +#include <asm/mpc85xx.h>

> >  #include <asm/mpic_timer.h>

> 

> So much for, "The driver currently is only tested on fsl chip, but it can

> potentially support other global timers complying to OpenPIC standard."

> 

> >  #define FSL_GLOBAL_TIMER		0x1

> > @@ -71,8 +74,10 @@ struct timer_group_priv {

> >  	struct timer_regs __iomem	*regs;

> >  	struct mpic_timer		timer[TIMERS_PER_GROUP];

> >  	struct list_head		node;

> > +	unsigned long			idle;

> >  	unsigned int			timerfreq;

> > -	unsigned int			idle;

> 

> Why?

> 


Um... It shouldn't be happened...i will remove this.

> > +	unsigned int			suspended_timerfreq;

> > +	unsigned int			resume_timerfreq;

> >  	unsigned int			flags;

> >  	spinlock_t			lock;

> >  	void __iomem			*group_tcr;

> > @@ -88,6 +93,7 @@ static struct cascade_priv cascade_timer[] = {  };

> >

> >  static LIST_HEAD(timer_group_list);

> > +static int switch_freq_flag;

> 

> Needs documentation, and based on "_flag" it should probably be a bool.

> 


Okay.

> >  static void convert_ticks_to_time(struct timer_group_priv *priv,

> >  		const u64 ticks, struct timeval *time) @@ -423,6 +429,33 @@ struct

> > mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev,  }

> > EXPORT_SYMBOL(mpic_request_timer);

> >

> > +static void timer_group_get_suspended_freq(struct timer_group_priv

> > +*priv) {

> > +	struct device_node *np;

> > +

> > +	np = of_find_compatible_node(NULL, NULL, "fsl,qoriq-clockgen-2.0");

> > +	if (!np) {

> > +		pr_err("mpic timer: Missing clockgen device node.\n");

> 

> Why is it an error to not have a 2.0 QorIQ clockgen?

> 


This will affect the accuracy of the timer. But not means the timer cannot work.
Maybe you are right, this pr_err should be a pr_warn.

> > +		return;

> > +	}

> > +

> > +	of_property_read_u32(np, "clock-frequency", &priv->suspended_timerfreq);

> > +	of_node_put(np);

> 

> Shouldn't this go through the clock API?

> 


Sorry, I'm not clear about clock API, you mean fsl_get_sys_freq()? Or ?

The timer operates on sys_ref_clk frequency during deep sleep. And The timer runs on
platform clock/2 during normal operation.

fsl_get_sys_freq() can get platform clock, but cannot get sys_ref_clk.

> > +}

> > +

> > +static int need_to_switch_freq(void)

> > +{

> > +	u32 svr;

> > +

> > +	svr = mfspr(SPRN_SVR);

> > +	if (SVR_SOC_VER(svr) == SVR_T1040 ||

> > +			SVR_SOC_VER(svr) == SVR_T1042)

> > +		return 1;

> 

> Explain why this is specific to T104x.

> 


Mpic timer freq will be change when system going to deep sleep. So we need to recalculate the time.

> -Scott

>
Scott Wood April 15, 2014, 9:06 p.m. UTC | #3
On Mon, 2014-04-14 at 22:23 -0500, Wang Dongsheng-B40534 wrote:
> 
> > -----Original Message-----
> > From: Wood Scott-B07421
> > Sent: Tuesday, April 15, 2014 7:36 AM
> > To: Wang Dongsheng-B40534
> > Cc: Jin Zhengxiong-R64188; Li Yang-Leo-R58472; Zhao Chenhui-B35336; linuxppc-
> > dev@lists.ozlabs.org
> > Subject: Re: [PATCH 2/2] fsl/mpic_timer: make mpic_timer to support deep sleep
> > feature
> > 
> > On Mon, 2014-04-14 at 10:24 +0800, Dongsheng Wang wrote:
> > > From: Wang Dongsheng <dongsheng.wang@freescale.com>
> > >
> > > At T104x platfrom the timer clock will be changed when system going to
> > > deep sleep.
> > 
> > Could you elaborate on what is changing and why?
> > 
> 
> Okay.
> 
> > > +#include <asm/mpc85xx.h>
> > >  #include <asm/mpic_timer.h>
> > 
> > So much for, "The driver currently is only tested on fsl chip, but it can
> > potentially support other global timers complying to OpenPIC standard."
> > 
> > >  #define FSL_GLOBAL_TIMER		0x1
> > > @@ -71,8 +74,10 @@ struct timer_group_priv {
> > >  	struct timer_regs __iomem	*regs;
> > >  	struct mpic_timer		timer[TIMERS_PER_GROUP];
> > >  	struct list_head		node;
> > > +	unsigned long			idle;
> > >  	unsigned int			timerfreq;
> > > -	unsigned int			idle;
> > 
> > Why?
> > 
> 
> Um... It shouldn't be happened...i will remove this.
> 
> > > +	unsigned int			suspended_timerfreq;
> > > +	unsigned int			resume_timerfreq;
> > >  	unsigned int			flags;
> > >  	spinlock_t			lock;
> > >  	void __iomem			*group_tcr;
> > > @@ -88,6 +93,7 @@ static struct cascade_priv cascade_timer[] = {  };
> > >
> > >  static LIST_HEAD(timer_group_list);
> > > +static int switch_freq_flag;
> > 
> > Needs documentation, and based on "_flag" it should probably be a bool.
> > 
> 
> Okay.
> 
> > >  static void convert_ticks_to_time(struct timer_group_priv *priv,
> > >  		const u64 ticks, struct timeval *time) @@ -423,6 +429,33 @@ struct
> > > mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev,  }
> > > EXPORT_SYMBOL(mpic_request_timer);
> > >
> > > +static void timer_group_get_suspended_freq(struct timer_group_priv
> > > +*priv) {
> > > +	struct device_node *np;
> > > +
> > > +	np = of_find_compatible_node(NULL, NULL, "fsl,qoriq-clockgen-2.0");
> > > +	if (!np) {
> > > +		pr_err("mpic timer: Missing clockgen device node.\n");
> > 
> > Why is it an error to not have a 2.0 QorIQ clockgen?
> > 
> 
> This will affect the accuracy of the timer. But not means the timer cannot work.
> Maybe you are right, this pr_err should be a pr_warn.

What I mean is, what if the mpic timer driver is used with deep sleep on
a different chip such as mpc8536?

> > > +		return;
> > > +	}
> > > +
> > > +	of_property_read_u32(np, "clock-frequency", &priv->suspended_timerfreq);
> > > +	of_node_put(np);
> > 
> > Shouldn't this go through the clock API?
> > 
> 
> Sorry, I'm not clear about clock API, you mean fsl_get_sys_freq()? Or ?

No, I mean drivers/clk/

Though I suppose manually reading clock-frequency is OK, as the
clock-frequency on the clockgen node predated the introduction of clock
bindings to the device tree.

Don't use fsl_get_sys_freq().

> The timer operates on sys_ref_clk frequency during deep sleep. And The timer runs on
> platform clock/2 during normal operation.

Sigh...  I wish hardware people would consult us before doing screwy
things like this.  If the platform clock is unavailable in deep sleep
(Can that really be true?  Surely there are other wakeup sources that
need it), why not run it at sys_ref_clk all the time?

Where is this documented?

> > > +static int need_to_switch_freq(void)
> > > +{
> > > +	u32 svr;
> > > +
> > > +	svr = mfspr(SPRN_SVR);
> > > +	if (SVR_SOC_VER(svr) == SVR_T1040 ||
> > > +			SVR_SOC_VER(svr) == SVR_T1042)
> > > +		return 1;
> > 
> > Explain why this is specific to T104x.
> > 
> 
> Mpic timer freq will be change when system going to deep sleep. So we need to recalculate the time.

Do any other chips with deep sleep have this problem?

-Scott
Dongsheng Wang April 16, 2014, 6:06 a.m. UTC | #4
> -----Original Message-----

> From: Wood Scott-B07421

> Sent: Wednesday, April 16, 2014 5:06 AM

> To: Wang Dongsheng-B40534

> Cc: Jin Zhengxiong-R64188; Li Yang-Leo-R58472; Zhao Chenhui-B35336; linuxppc-

> dev@lists.ozlabs.org

> Subject: Re: [PATCH 2/2] fsl/mpic_timer: make mpic_timer to support deep sleep

> feature

> 

> On Mon, 2014-04-14 at 22:23 -0500, Wang Dongsheng-B40534 wrote:

> >

> > > -----Original Message-----

> > > From: Wood Scott-B07421

> > > Sent: Tuesday, April 15, 2014 7:36 AM

> > > To: Wang Dongsheng-B40534

> > > Cc: Jin Zhengxiong-R64188; Li Yang-Leo-R58472; Zhao Chenhui-B35336;

> linuxppc-

> > > dev@lists.ozlabs.org

> > > Subject: Re: [PATCH 2/2] fsl/mpic_timer: make mpic_timer to support deep

> sleep

> > > feature

> > >

> > > On Mon, 2014-04-14 at 10:24 +0800, Dongsheng Wang wrote:

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

> > > >

> > > > At T104x platfrom the timer clock will be changed when system going to

> > > > deep sleep.

> > >

> > > Could you elaborate on what is changing and why?

> > >

> >

> > Okay.

> >

> > > > +#include <asm/mpc85xx.h>

> > > >  #include <asm/mpic_timer.h>

> > >

> > > So much for, "The driver currently is only tested on fsl chip, but it can

> > > potentially support other global timers complying to OpenPIC standard."

> > >

> > > >  #define FSL_GLOBAL_TIMER		0x1

> > > > @@ -71,8 +74,10 @@ struct timer_group_priv {

> > > >  	struct timer_regs __iomem	*regs;

> > > >  	struct mpic_timer		timer[TIMERS_PER_GROUP];

> > > >  	struct list_head		node;

> > > > +	unsigned long			idle;

> > > >  	unsigned int			timerfreq;

> > > > -	unsigned int			idle;

> > >

> > > Why?

> > >

> >

> > Um... It shouldn't be happened...i will remove this.

> >

> > > > +	unsigned int			suspended_timerfreq;

> > > > +	unsigned int			resume_timerfreq;

> > > >  	unsigned int			flags;

> > > >  	spinlock_t			lock;

> > > >  	void __iomem			*group_tcr;

> > > > @@ -88,6 +93,7 @@ static struct cascade_priv cascade_timer[] = {  };

> > > >

> > > >  static LIST_HEAD(timer_group_list);

> > > > +static int switch_freq_flag;

> > >

> > > Needs documentation, and based on "_flag" it should probably be a bool.

> > >

> >

> > Okay.

> >

> > > >  static void convert_ticks_to_time(struct timer_group_priv *priv,

> > > >  		const u64 ticks, struct timeval *time) @@ -423,6 +429,33 @@

> struct

> > > > mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev,  }

> > > > EXPORT_SYMBOL(mpic_request_timer);

> > > >

> > > > +static void timer_group_get_suspended_freq(struct timer_group_priv

> > > > +*priv) {

> > > > +	struct device_node *np;

> > > > +

> > > > +	np = of_find_compatible_node(NULL, NULL, "fsl,qoriq-clockgen-2.0");

> > > > +	if (!np) {

> > > > +		pr_err("mpic timer: Missing clockgen device node.\n");

> > >

> > > Why is it an error to not have a 2.0 QorIQ clockgen?

> > >

> >

> > This will affect the accuracy of the timer. But not means the timer cannot

> work.

> > Maybe you are right, this pr_err should be a pr_warn.

> 

> What I mean is, what if the mpic timer driver is used with deep sleep on

> a different chip such as mpc8536?

> 


Only T104x has this feature, other platform will not be effect.
I will remove this pr_err.

> > > > +		return;

> > > > +	}

> > > > +

> > > > +	of_property_read_u32(np, "clock-frequency", &priv-

> >suspended_timerfreq);

> > > > +	of_node_put(np);

> > >

> > > Shouldn't this go through the clock API?

> > >

> >

> > Sorry, I'm not clear about clock API, you mean fsl_get_sys_freq()? Or ?

> 

> No, I mean drivers/clk/

> 

> Though I suppose manually reading clock-frequency is OK, as the

> clock-frequency on the clockgen node predated the introduction of clock

> bindings to the device tree.

> 

> Don't use fsl_get_sys_freq().

> 


No, we cannot use drivers/clk/. Because clk-ppc-corenet.c only support corenet platform.

> > > > +static int need_to_switch_freq(void)

> > > > +{

> > > > +	u32 svr;

> > > > +

> > > > +	svr = mfspr(SPRN_SVR);

> > > > +	if (SVR_SOC_VER(svr) == SVR_T1040 ||

> > > > +			SVR_SOC_VER(svr) == SVR_T1042)

> > > > +		return 1;

> > >

> > > Explain why this is specific to T104x.

> > >

> >

> > Mpic timer freq will be change when system going to deep sleep. So we need to

> recalculate the time.

> 

> Do any other chips with deep sleep have this problem?

> 


Only has on T104x right now.

Regards,
-Dongsheng

> -Scott

>
diff mbox

Patch

diff --git a/arch/powerpc/sysdev/mpic_timer.c b/arch/powerpc/sysdev/mpic_timer.c
index 9d9b062..737a53d 100644
--- a/arch/powerpc/sysdev/mpic_timer.c
+++ b/arch/powerpc/sysdev/mpic_timer.c
@@ -11,6 +11,7 @@ 
  * option) any later version.
  */
 
+#include <linux/fsl/fsl_pm.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/module.h>
@@ -18,6 +19,7 @@ 
 #include <linux/mm.h>
 #include <linux/interrupt.h>
 #include <linux/slab.h>
+#include <linux/suspend.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_device.h>
@@ -26,6 +28,7 @@ 
 #include <sysdev/fsl_soc.h>
 #include <asm/io.h>
 
+#include <asm/mpc85xx.h>
 #include <asm/mpic_timer.h>
 
 #define FSL_GLOBAL_TIMER		0x1
@@ -71,8 +74,10 @@  struct timer_group_priv {
 	struct timer_regs __iomem	*regs;
 	struct mpic_timer		timer[TIMERS_PER_GROUP];
 	struct list_head		node;
+	unsigned long			idle;
 	unsigned int			timerfreq;
-	unsigned int			idle;
+	unsigned int			suspended_timerfreq;
+	unsigned int			resume_timerfreq;
 	unsigned int			flags;
 	spinlock_t			lock;
 	void __iomem			*group_tcr;
@@ -88,6 +93,7 @@  static struct cascade_priv cascade_timer[] = {
 };
 
 static LIST_HEAD(timer_group_list);
+static int switch_freq_flag;
 
 static void convert_ticks_to_time(struct timer_group_priv *priv,
 		const u64 ticks, struct timeval *time)
@@ -423,6 +429,33 @@  struct mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev,
 }
 EXPORT_SYMBOL(mpic_request_timer);
 
+static void timer_group_get_suspended_freq(struct timer_group_priv *priv)
+{
+	struct device_node *np;
+
+	np = of_find_compatible_node(NULL, NULL, "fsl,qoriq-clockgen-2.0");
+	if (!np) {
+		pr_err("mpic timer: Missing clockgen device node.\n");
+
+		return;
+	}
+
+	of_property_read_u32(np, "clock-frequency", &priv->suspended_timerfreq);
+	of_node_put(np);
+}
+
+static int need_to_switch_freq(void)
+{
+	u32 svr;
+
+	svr = mfspr(SPRN_SVR);
+	if (SVR_SOC_VER(svr) == SVR_T1040 ||
+			SVR_SOC_VER(svr) == SVR_T1042)
+		return 1;
+
+	return 0;
+}
+
 static int timer_group_get_freq(struct device_node *np,
 			struct timer_group_priv *priv)
 {
@@ -437,6 +470,15 @@  static int timer_group_get_freq(struct device_node *np,
 					&priv->timerfreq);
 			of_node_put(dn);
 		}
+
+		/*
+		 * For deep sleep, if system going to deep sleep,
+		 * timer freq will be changed.
+		 */
+		if (need_to_switch_freq()) {
+			timer_group_get_suspended_freq(priv);
+			switch_freq_flag = 1;
+		}
 	}
 
 	if (priv->timerfreq <= 0)
@@ -445,6 +487,7 @@  static int timer_group_get_freq(struct device_node *np,
 	if (priv->flags & FSL_GLOBAL_TIMER) {
 		div = (1 << (MPIC_TIMER_TCR_CLKDIV >> 8)) * 8;
 		priv->timerfreq /= div;
+		priv->suspended_timerfreq /= div;
 	}
 
 	return 0;
@@ -564,14 +607,190 @@  out:
 	kfree(priv);
 }
 
+static void mpic_reset_time(struct mpic_timer *handle, struct timeval *bcr_time,
+				struct timeval *ccr_time)
+{
+	struct timer_group_priv *priv = container_of(handle,
+			struct timer_group_priv, timer[handle->num]);
+
+	u64 ccr_ticks = 0;
+	u64 bcr_ticks = 0;
+
+	/* switch bcr time */
+	convert_time_to_ticks(priv, bcr_time, &bcr_ticks);
+
+	/* switch ccr time */
+	convert_time_to_ticks(priv, ccr_time, &ccr_ticks);
+
+	if (handle->cascade_handle) {
+		u32 tmp_ticks;
+		u32 rem_ticks;
+
+		/* reset ccr ticks to bcr */
+		tmp_ticks = div_u64_rem(ccr_ticks, MAX_TICKS_CASCADE,
+					&rem_ticks);
+		out_be32(&priv->regs[handle->num].gtbcr,
+			tmp_ticks | TIMER_STOP);
+		out_be32(&priv->regs[handle->num - 1].gtbcr, rem_ticks);
+
+		/* start timer */
+		clrbits32(&priv->regs[handle->num].gtbcr, TIMER_STOP);
+
+		/* reset bcr */
+		tmp_ticks = div_u64_rem(bcr_ticks, MAX_TICKS_CASCADE,
+					&rem_ticks);
+		out_be32(&priv->regs[handle->num].gtbcr,
+			tmp_ticks & ~TIMER_STOP);
+		out_be32(&priv->regs[handle->num - 1].gtbcr, rem_ticks);
+	} else {
+		/* reset ccr ticks to bcr */
+		out_be32(&priv->regs[handle->num].gtbcr,
+			ccr_ticks | TIMER_STOP);
+		/* start timer */
+		clrbits32(&priv->regs[handle->num].gtbcr, TIMER_STOP);
+		/* reset bcr */
+		out_be32(&priv->regs[handle->num].gtbcr,
+			bcr_ticks & ~TIMER_STOP);
+	}
+}
+
+static void do_switch_time(struct mpic_timer *handle, unsigned int new_freq)
+{
+	struct timer_group_priv *priv = container_of(handle,
+			struct timer_group_priv, timer[handle->num]);
+	struct timeval ccr_time;
+	struct timeval bcr_time;
+	unsigned int timerfreq;
+	u32 test_stop;
+	u64 ticks;
+
+	test_stop = in_be32(&priv->regs[handle->num].gtbcr);
+	test_stop &= TIMER_STOP;
+	if (test_stop)
+		return;
+
+	/* stop timer, prepare reset time */
+	setbits32(&priv->regs[handle->num].gtbcr, TIMER_STOP);
+
+	/* get bcr time */
+	if (handle->cascade_handle) {
+		u32 tmp_ticks;
+
+		tmp_ticks = in_be32(&priv->regs[handle->num].gtbcr);
+		tmp_ticks &= ~TIMER_STOP;
+		ticks = ((u64)tmp_ticks & UINT_MAX) * (u64)MAX_TICKS_CASCADE;
+		tmp_ticks = in_be32(&priv->regs[handle->num - 1].gtbcr);
+		ticks += tmp_ticks;
+	} else {
+		ticks = in_be32(&priv->regs[handle->num].gtbcr);
+		ticks &= ~TIMER_STOP;
+	}
+	convert_ticks_to_time(priv, ticks, &bcr_time);
+
+	/* get ccr time */
+	mpic_get_remain_time(handle, &ccr_time);
+
+	/* recalculate timer time */
+	timerfreq = priv->timerfreq;
+	priv->timerfreq = new_freq;
+	mpic_reset_time(handle, &bcr_time, &ccr_time);
+	priv->timerfreq = timerfreq;
+}
+
+static void switch_group_timer(struct timer_group_priv *priv,
+				unsigned int new_freq)
+{
+	int i, num;
+
+	for (i = 0; i < TIMERS_PER_GROUP; i++) {
+		num = TIMERS_PER_GROUP - 1 - i;
+		/* cascade */
+		if ((i + 1) < TIMERS_PER_GROUP &&
+				priv->timer[num].cascade_handle) {
+			do_switch_time(&priv->timer[num], new_freq);
+			i++;
+			continue;
+		}
+
+		if (!test_bit(i, &priv->idle))
+			do_switch_time(&priv->timer[num], new_freq);
+	}
+}
+
+static int mpic_timer_suspend(void)
+{
+	struct timer_group_priv *priv;
+	suspend_state_t pm_state;
+
+	pm_state = pm_suspend_state();
+
+	list_for_each_entry(priv, &timer_group_list, node) {
+		/* timer not be used */
+		if (priv->idle == 0xf)
+			continue;
+
+		switch (pm_state) {
+		case PM_SUSPEND_STANDBY:
+			break;
+		case PM_SUSPEND_MEM:
+			if (!switch_freq_flag)
+				continue;
+
+			if (!priv->suspended_timerfreq) {
+				pr_warn("Mpic timer will not be accurate.\n");
+				continue;
+			}
+
+			/* will switch timers, a set of timer */
+			switch_group_timer(priv, priv->suspended_timerfreq);
+
+			/* Software: switch timerfreq to suspended freq */
+			priv->resume_timerfreq = priv->timerfreq;
+			priv->timerfreq = priv->suspended_timerfreq;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
 static void mpic_timer_resume(void)
 {
 	struct timer_group_priv *priv;
+	suspend_state_t pm_state;
+
+	pm_state = pm_suspend_state();
 
 	list_for_each_entry(priv, &timer_group_list, node) {
 		/* Init FSL timer hardware */
 		if (priv->flags & FSL_GLOBAL_TIMER)
 			setbits32(priv->group_tcr, MPIC_TIMER_TCR_CLKDIV);
+
+		/* timer not be used */
+		if (priv->idle == 0xf)
+			continue;
+
+		switch (pm_state) {
+		case PM_SUSPEND_STANDBY:
+			break;
+		case PM_SUSPEND_MEM:
+			if (!switch_freq_flag)
+				continue;
+
+			if (!priv->suspended_timerfreq)
+				continue;
+
+			/* will switch timers, a set of timer */
+			switch_group_timer(priv, priv->resume_timerfreq);
+
+			/* restore timerfreq */
+			priv->timerfreq = priv->resume_timerfreq;
+			break;
+		default:
+			break;
+		}
 	}
 }
 
@@ -581,6 +800,7 @@  static const struct of_device_id mpic_timer_ids[] = {
 };
 
 static struct syscore_ops mpic_timer_syscore_ops = {
+	.suspend = mpic_timer_suspend,
 	.resume = mpic_timer_resume,
 };