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

At T104x platfrom the timer clock will be changed from platform_clock
to sys_ref_clock when system going to deep sleep.

So before system going to deep sleep, we need to change time to adapt
to the new frequency that is sys_ref_clock. And after resume from deep
sleep, restore the time that based on platform_clock.

Signed-off-by: Wang Dongsheng <dongsheng.w...@freescale.com>
---
*v2*
Remove some unnecessary warning message.
Remove "switch_freq_flag", it's unnecessary.

Modify the description of the patch.

diff --git a/arch/powerpc/sysdev/mpic_timer.c b/arch/powerpc/sysdev/mpic_timer.c
index 9d9b062..71ad368 100644
--- a/arch/powerpc/sysdev/mpic_timer.c
+++ b/arch/powerpc/sysdev/mpic_timer.c
@@ -18,6 +18,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,7 +27,9 @@
 #include <sysdev/fsl_soc.h>
 #include <asm/io.h>
 
+#include <asm/mpc85xx.h>
 #include <asm/mpic_timer.h>
+#include <asm/pm.h>
 
 #define FSL_GLOBAL_TIMER               0x1
 
@@ -72,6 +75,8 @@ struct timer_group_priv {
        struct mpic_timer               timer[TIMERS_PER_GROUP];
        struct list_head                node;
        unsigned int                    timerfreq;
+       unsigned int                    suspended_timerfreq;
+       unsigned int                    resume_timerfreq;
        unsigned int                    idle;
        unsigned int                    flags;
        spinlock_t                      lock;
@@ -423,6 +428,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)
+               return;
+
+       of_property_read_u32(np, "clock-frequency", &priv->suspended_timerfreq);
+       of_node_put(np);
+
+       if (!priv->suspended_timerfreq)
+               pr_warn("Mpic timer will not be accurate during deep sleep.\n");
+}
+
+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 +469,13 @@ static int timer_group_get_freq(struct device_node *np,
                                        &priv->timerfreq);
                        of_node_put(dn);
                }
+
+               /*
+                * For deep sleep, if system goes to deep sleep,
+                * timer freq will be changed.
+                */
+               if (need_to_switch_freq())
+                       timer_group_get_suspended_freq(priv);
        }
 
        if (priv->timerfreq <= 0)
@@ -445,6 +484,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 +604,182 @@ 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, (unsigned long *)&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 (!priv->suspended_timerfreq)
+                               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 (!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 +789,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,
 };
 
-- 
1.8.5

_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev

Reply via email to