summaryrefslogtreecommitdiff
path: root/arch/powerpc/sysdev
diff options
context:
space:
mode:
authorWang Dongsheng <dongsheng.wang@freescale.com>2014-04-25 05:28:36 (GMT)
committerJose Rivera <German.Rivera@freescale.com>2014-04-29 14:12:55 (GMT)
commit967c39a59a9b78af06b1e8d335935ed93769f08c (patch)
tree3226fad64455808d122d00d4e76c4c898f8d2ddf /arch/powerpc/sysdev
parentacf2570649581aa9b12a1afcb73215c439ab33c8 (diff)
downloadlinux-fsl-qoriq-967c39a59a9b78af06b1e8d335935ed93769f08c.tar.xz
fsl/mpic_timer: make mpic_timer to support deep sleep feature
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.wang@freescale.com> Change-Id: I15608aa39df70ab6ca28c8dbed3b0cbf6d71343e Reviewed-on: http://git.am.freescale.net:8181/11531 Tested-by: Review Code-CDREVIEW <CDREVIEW@freescale.com> Reviewed-by: Yang Li <LeoLi@freescale.com> Reviewed-by: Jose Rivera <German.Rivera@freescale.com>
Diffstat (limited to 'arch/powerpc/sysdev')
-rw-r--r--arch/powerpc/sysdev/mpic_timer.c211
1 files changed, 210 insertions, 1 deletions
diff --git a/arch/powerpc/sysdev/mpic_timer.c b/arch/powerpc/sysdev/mpic_timer.c
index 831c24a..5553907 100644
--- a/arch/powerpc/sysdev/mpic_timer.c
+++ b/arch/powerpc/sysdev/mpic_timer.c
@@ -18,13 +18,16 @@
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
+#include <linux/suspend.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/syscore_ops.h>
#include <sysdev/fsl_soc.h>
#include <asm/io.h>
+#include <asm/mpc85xx.h>
#include <asm/mpic_timer.h>
+#include <asm/fsl_pm.h>
#define FSL_GLOBAL_TIMER 0x1
@@ -69,8 +72,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;
@@ -421,6 +426,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)
{
@@ -435,6 +467,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)
@@ -443,6 +482,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;
@@ -562,14 +602,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, &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;
+ }
}
}
@@ -579,6 +787,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,
};