diff options
author | Wang Dongsheng <dongsheng.wang@freescale.com> | 2014-04-25 05:28:36 (GMT) |
---|---|---|
committer | Jose Rivera <German.Rivera@freescale.com> | 2014-04-29 14:12:55 (GMT) |
commit | 967c39a59a9b78af06b1e8d335935ed93769f08c (patch) | |
tree | 3226fad64455808d122d00d4e76c4c898f8d2ddf /arch/powerpc/sysdev | |
parent | acf2570649581aa9b12a1afcb73215c439ab33c8 (diff) | |
download | linux-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.c | 211 |
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, }; |