Sekhar Nori <[email protected]> writes:

> This patch adds core power management (suspend-to-RAM)
> support for DaVinci SoCs.
>
> The code depends on the the "deepsleep" feature to suspend
> the SoC and saves power by gating the input clock.
>
> The wakeup can be based on an external event as supported
> by the SoC.
>
> Assembly code (in sleep.S) is added to aid gating DDR2
> clocks. Code doing this work should not be accessing DDR2.
> The assembly code is relocated to SRAM by the code in pm.c
>
> The support has been validated on DA850/OMAP-L138 only
> though the code is (hopefully) generic enough that other
> SoCs supporting deepsleep feature simply requires SoC
> specific code to start using this driver.
>
> Note that all the device drivers don't support suspend/resume
> still and are being worked on.
>
> Signed-off-by: Sekhar Nori <[email protected]>

This series looks good.  Applying v3 to davinci-git and queuing for
2.6.34 in davinci-next.

Kevin

> ---
> Since v1:
> 1) suspend function has been renamed.
> 2) register access helper functions have been removed.
> 3) spinlock in davinci_pm_suspend() function has been removed.
>
>  arch/arm/mach-davinci/Makefile              |    1 +
>  arch/arm/mach-davinci/include/mach/memory.h |    1 +
>  arch/arm/mach-davinci/include/mach/pm.h     |   54 +++++++
>  arch/arm/mach-davinci/pm.c                  |  158 +++++++++++++++++++
>  arch/arm/mach-davinci/sleep.S               |  224 
> +++++++++++++++++++++++++++
>  5 files changed, 438 insertions(+), 0 deletions(-)
>  create mode 100644 arch/arm/mach-davinci/include/mach/pm.h
>  create mode 100644 arch/arm/mach-davinci/pm.c
>  create mode 100644 arch/arm/mach-davinci/sleep.S
>
> diff --git a/arch/arm/mach-davinci/Makefile b/arch/arm/mach-davinci/Makefile
> index eeb9230..d0fed3a 100644
> --- a/arch/arm/mach-davinci/Makefile
> +++ b/arch/arm/mach-davinci/Makefile
> @@ -34,3 +34,4 @@ obj-$(CONFIG_MACH_DAVINCI_DA850_EVM)        += 
> board-da850-evm.o
>  # Power Management
>  obj-$(CONFIG_CPU_FREQ)                       += cpufreq.o
>  obj-$(CONFIG_CPU_IDLE)                       += cpuidle.o
> +obj-$(CONFIG_SUSPEND)                        += pm.o sleep.o
> diff --git a/arch/arm/mach-davinci/include/mach/memory.h 
> b/arch/arm/mach-davinci/include/mach/memory.h
> index 7aeaf46..a91edfb 100644
> --- a/arch/arm/mach-davinci/include/mach/memory.h
> +++ b/arch/arm/mach-davinci/include/mach/memory.h
> @@ -33,6 +33,7 @@
>  
>  #define DDR2_SDRCR_OFFSET    0xc
>  #define DDR2_SRPD_BIT                BIT(23)
> +#define DDR2_MCLKSTOPEN_BIT  BIT(30)
>  #define DDR2_LPMODEN_BIT     BIT(31)
>  
>  /*
> diff --git a/arch/arm/mach-davinci/include/mach/pm.h 
> b/arch/arm/mach-davinci/include/mach/pm.h
> new file mode 100644
> index 0000000..37b19bf
> --- /dev/null
> +++ b/arch/arm/mach-davinci/include/mach/pm.h
> @@ -0,0 +1,54 @@
> +/*
> + * TI DaVinci platform support for power management.
> + *
> + * Copyright (C) 2009 Texas Instruments, Inc. http://www.ti.com/
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation version 2.
> + *
> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any
> + * kind, whether express or implied; without even the implied warranty
> + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef _MACH_DAVINCI_PM_H
> +#define _MACH_DAVINCI_PM_H
> +
> +/*
> + * Caution: Assembly code in sleep.S makes assumtion on the order
> + * of the members of this structure.
> + */
> +struct davinci_pm_config {
> +     void __iomem *ddr2_ctlr_base;
> +     void __iomem *ddrpsc_reg_base;
> +     int ddrpsc_num;
> +     void __iomem *ddrpll_reg_base;
> +     void __iomem *deepsleep_reg;
> +     void __iomem *cpupll_reg_base;
> +     /*
> +      * Note on SLEEPCOUNT:
> +      * The SLEEPCOUNT feature is mainly intended for cases in which
> +      * the internal oscillator is used. The internal oscillator is
> +      * fully disabled in deep sleep mode.  When you exist deep sleep
> +      * mode, the oscillator will be turned on and will generate very
> +      * small oscillations which will not be detected by the deep sleep
> +      * counter.  Eventually those oscillations will grow to an amplitude
> +      * large enough to start incrementing the deep sleep counter.
> +      * In this case recommendation from hardware engineers is that the
> +      * SLEEPCOUNT be set to 4096.  This means that 4096 valid clock cycles
> +      * must be detected before the clock is passed to the rest of the
> +      * system.
> +      * In the case that the internal oscillator is not used and the
> +      * clock is generated externally, the SLEEPCOUNT value can be very
> +      * small since the clock input is assumed to be stable before SoC
> +      * is taken out of deepsleep mode.  A value of 128 would be more than
> +      * adequate.
> +      */
> +     int sleepcount;
> +};
> +
> +extern unsigned int davinci_cpu_suspend_sz;
> +extern void davinci_cpu_suspend(struct davinci_pm_config *);
> +
> +#endif
> diff --git a/arch/arm/mach-davinci/pm.c b/arch/arm/mach-davinci/pm.c
> new file mode 100644
> index 0000000..fab953b
> --- /dev/null
> +++ b/arch/arm/mach-davinci/pm.c
> @@ -0,0 +1,158 @@
> +/*
> + * DaVinci Power Management Routines
> + *
> + * Copyright (C) 2009 Texas Instruments, Inc. http://www.ti.com/
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/pm.h>
> +#include <linux/suspend.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/spinlock.h>
> +
> +#include <asm/cacheflush.h>
> +#include <asm/delay.h>
> +
> +#include <mach/da8xx.h>
> +#include <mach/sram.h>
> +#include <mach/pm.h>
> +
> +#include "clock.h"
> +
> +#define DEEPSLEEP_SLEEPCOUNT_MASK    0xFFFF
> +
> +static void (*davinci_sram_suspend) (struct davinci_pm_config *);
> +static struct davinci_pm_config *pdata;
> +
> +static void davinci_sram_push(void *dest, void *src, unsigned int size)
> +{
> +     memcpy(dest, src, size);
> +     flush_icache_range((unsigned long)dest, (unsigned long)(dest + size));
> +}
> +
> +static void davinci_pm_suspend(void)
> +{
> +     unsigned val;
> +
> +     if (pdata->cpupll_reg_base != pdata->ddrpll_reg_base) {
> +
> +             /* Switch CPU PLL to bypass mode */
> +             val = __raw_readl(pdata->cpupll_reg_base + PLLCTL);
> +             val &= ~(PLLCTL_PLLENSRC | PLLCTL_PLLEN);
> +             __raw_writel(val, pdata->cpupll_reg_base + PLLCTL);
> +
> +             udelay(PLL_BYPASS_TIME);
> +
> +             /* Powerdown CPU PLL */
> +             val = __raw_readl(pdata->cpupll_reg_base + PLLCTL);
> +             val |= PLLCTL_PLLPWRDN;
> +             __raw_writel(val, pdata->cpupll_reg_base + PLLCTL);
> +     }
> +
> +     /* Configure sleep count in deep sleep register */
> +     val = __raw_readl(pdata->deepsleep_reg);
> +     val &= ~DEEPSLEEP_SLEEPCOUNT_MASK,
> +     val |= pdata->sleepcount;
> +     __raw_writel(val, pdata->deepsleep_reg);
> +
> +     /* System goes to sleep in this call */
> +     davinci_sram_suspend(pdata);
> +
> +     if (pdata->cpupll_reg_base != pdata->ddrpll_reg_base) {
> +
> +             /* put CPU PLL in reset */
> +             val = __raw_readl(pdata->cpupll_reg_base + PLLCTL);
> +             val &= ~PLLCTL_PLLRST;
> +             __raw_writel(val, pdata->cpupll_reg_base + PLLCTL);
> +
> +             /* put CPU PLL in power down */
> +             val = __raw_readl(pdata->cpupll_reg_base + PLLCTL);
> +             val &= ~PLLCTL_PLLPWRDN;
> +             __raw_writel(val, pdata->cpupll_reg_base + PLLCTL);
> +
> +             /* wait for CPU PLL reset */
> +             udelay(PLL_RESET_TIME);
> +
> +             /* bring CPU PLL out of reset */
> +             val = __raw_readl(pdata->cpupll_reg_base + PLLCTL);
> +             val |= PLLCTL_PLLRST;
> +             __raw_writel(val, pdata->cpupll_reg_base + PLLCTL);
> +
> +             /* Wait for CPU PLL to lock */
> +             udelay(PLL_LOCK_TIME);
> +
> +             /* Remove CPU PLL from bypass mode */
> +             val = __raw_readl(pdata->cpupll_reg_base + PLLCTL);
> +             val &= ~PLLCTL_PLLENSRC;
> +             val |= PLLCTL_PLLEN;
> +             __raw_writel(val, pdata->cpupll_reg_base + PLLCTL);
> +     }
> +}
> +
> +static int davinci_pm_enter(suspend_state_t state)
> +{
> +     int ret = 0;
> +
> +     switch (state) {
> +     case PM_SUSPEND_STANDBY:
> +     case PM_SUSPEND_MEM:
> +             davinci_pm_suspend();
> +             break;
> +     default:
> +             ret = -EINVAL;
> +     }
> +
> +     return ret;
> +}
> +
> +static struct platform_suspend_ops davinci_pm_ops = {
> +     .enter          = davinci_pm_enter,
> +     .valid          = suspend_valid_only_mem,
> +};
> +
> +static int __init davinci_pm_probe(struct platform_device *pdev)
> +{
> +     pdata = pdev->dev.platform_data;
> +     if (!pdata) {
> +             dev_err(&pdev->dev, "cannot get platform data\n");
> +             return -ENOENT;
> +     }
> +
> +     davinci_sram_suspend = sram_alloc(davinci_cpu_suspend_sz, NULL);
> +     if (!davinci_sram_suspend) {
> +             dev_err(&pdev->dev, "cannot allocate SRAM memory\n");
> +             return -ENOMEM;
> +     }
> +
> +     davinci_sram_push(davinci_sram_suspend, davinci_cpu_suspend,
> +                                             davinci_cpu_suspend_sz);
> +
> +     suspend_set_ops(&davinci_pm_ops);
> +
> +     return 0;
> +}
> +
> +static int __exit davinci_pm_remove(struct platform_device *pdev)
> +{
> +     sram_free(davinci_sram_suspend, davinci_cpu_suspend_sz);
> +     return 0;
> +}
> +
> +static struct platform_driver davinci_pm_driver = {
> +     .driver = {
> +             .name    = "pm-davinci",
> +             .owner   = THIS_MODULE,
> +     },
> +     .remove = __exit_p(davinci_pm_remove),
> +};
> +
> +static int __init davinci_pm_init(void)
> +{
> +     return platform_driver_probe(&davinci_pm_driver, davinci_pm_probe);
> +}
> +late_initcall(davinci_pm_init);
> diff --git a/arch/arm/mach-davinci/sleep.S b/arch/arm/mach-davinci/sleep.S
> new file mode 100644
> index 0000000..fb5e72b
> --- /dev/null
> +++ b/arch/arm/mach-davinci/sleep.S
> @@ -0,0 +1,224 @@
> +/*
> + * (C) Copyright 2009, Texas Instruments, Inc. http://www.ti.com/
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR /PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
> + * MA 02111-1307 USA
> + */
> +
> +/* replicated define because linux/bitops.h cannot be included in assembly */
> +#define BIT(nr)                      (1 << (nr))
> +
> +#include <linux/linkage.h>
> +#include <asm/assembler.h>
> +#include <mach/psc.h>
> +#include <mach/memory.h>
> +
> +#include "clock.h"
> +
> +/* Arbitrary, hardware currently does not update PHYRDY correctly */
> +#define PHYRDY_CYCLES                0x1000
> +
> +/* Assume 25 MHz speed for the cycle conversions since PLLs are bypassed */
> +#define PLL_BYPASS_CYCLES    (PLL_BYPASS_TIME * 25)
> +#define PLL_RESET_CYCLES     (PLL_RESET_TIME * 25)
> +#define PLL_LOCK_CYCLES              (PLL_LOCK_TIME * 25)
> +
> +#define DEEPSLEEP_SLEEPENABLE_BIT    BIT(31)
> +
> +     .text
> +/*
> + * Move DaVinci into deep sleep state
> + *
> + * Note: This code is copied to internal SRAM by PM code. When the DaVinci
> + *    wakes up it continues execution at the point it went to sleep.
> + * Register Usage:
> + *   r0: contains virtual base for DDR2 controller
> + *   r1: contains virtual base for DDR2 Power and Sleep controller (PSC)
> + *   r2: contains PSC number for DDR2
> + *   r3: contains virtual base DDR2 PLL controller
> + *   r4: contains virtual address of the DEEPSLEEP register
> + */
> +ENTRY(davinci_cpu_suspend)
> +     stmfd   sp!, {r0-r12, lr}               @ save registers on stack
> +
> +     ldr     ip, CACHE_FLUSH
> +     blx     ip
> +
> +     ldmia   r0, {r0-r4}
> +
> +     /*
> +      * Switch DDR to self-refresh mode.
> +      */
> +
> +     /* calculate SDRCR address */
> +     ldr     ip, [r0, #DDR2_SDRCR_OFFSET]
> +     bic     ip, ip, #DDR2_SRPD_BIT
> +     orr     ip, ip, #DDR2_LPMODEN_BIT
> +     str     ip, [r0, #DDR2_SDRCR_OFFSET]
> +
> +     ldr     ip, [r0, #DDR2_SDRCR_OFFSET]
> +     orr     ip, ip, #DDR2_MCLKSTOPEN_BIT
> +     str     ip, [r0, #DDR2_SDRCR_OFFSET]
> +
> +       mov   ip, #PHYRDY_CYCLES
> +1:     subs  ip, ip, #0x1
> +       bne   1b
> +
> +       /* Disable DDR2 LPSC */
> +     mov     r7, r0
> +     mov     r0, #0x2
> +     bl davinci_ddr_psc_config
> +     mov     r0, r7
> +
> +     /* Disable clock to DDR PHY */
> +     ldr     ip, [r3, #PLLDIV1]
> +     bic     ip, ip, #PLLDIV_EN
> +     str     ip, [r3, #PLLDIV1]
> +
> +     /* Put the DDR PLL in bypass and power down */
> +     ldr     ip, [r3, #PLLCTL]
> +     bic     ip, ip, #PLLCTL_PLLENSRC
> +     bic     ip, ip, #PLLCTL_PLLEN
> +     str     ip, [r3, #PLLCTL]
> +
> +     /* Wait for PLL to switch to bypass */
> +       mov   ip, #PLL_BYPASS_CYCLES
> +2:     subs  ip, ip, #0x1
> +       bne   2b
> +
> +       /* Power down the PLL */
> +     ldr     ip, [r3, #PLLCTL]
> +     orr     ip, ip, #PLLCTL_PLLPWRDN
> +     str     ip, [r3, #PLLCTL]
> +
> +     /* Go to deep sleep */
> +     ldr     ip, [r4]
> +     orr     ip, ip, #DEEPSLEEP_SLEEPENABLE_BIT
> +     /* System goes to sleep beyond after this instruction */
> +     str     ip, [r4]
> +
> +     /* Wake up from sleep */
> +
> +     /* Clear sleep enable */
> +     ldr     ip, [r4]
> +     bic     ip, ip, #DEEPSLEEP_SLEEPENABLE_BIT
> +     str     ip, [r4]
> +
> +     /* initialize the DDR PLL controller */
> +
> +     /* Put PLL in reset */
> +     ldr     ip, [r3, #PLLCTL]
> +     bic     ip, ip, #PLLCTL_PLLRST
> +     str     ip, [r3, #PLLCTL]
> +
> +     /* Clear PLL power down */
> +     ldr     ip, [r3, #PLLCTL]
> +     bic     ip, ip, #PLLCTL_PLLPWRDN
> +     str     ip, [r3, #PLLCTL]
> +
> +       mov   ip, #PLL_RESET_CYCLES
> +3:     subs  ip, ip, #0x1
> +       bne   3b
> +
> +       /* Bring PLL out of reset */
> +     ldr     ip, [r3, #PLLCTL]
> +     orr     ip, ip, #PLLCTL_PLLRST
> +     str     ip, [r3, #PLLCTL]
> +
> +     /* Wait for PLL to lock (assume prediv = 1, 25MHz OSCIN) */
> +       mov   ip, #PLL_LOCK_CYCLES
> +4:     subs  ip, ip, #0x1
> +       bne   4b
> +
> +       /* Remove PLL from bypass mode */
> +     ldr     ip, [r3, #PLLCTL]
> +     bic     ip, ip, #PLLCTL_PLLENSRC
> +     orr     ip, ip, #PLLCTL_PLLEN
> +     str     ip, [r3, #PLLCTL]
> +
> +     /* Start 2x clock to DDR2 */
> +
> +     ldr     ip, [r3, #PLLDIV1]
> +     orr     ip, ip, #PLLDIV_EN
> +     str     ip, [r3, #PLLDIV1]
> +
> +     /* Enable VCLK */
> +
> +       /* Enable DDR2 LPSC */
> +     mov     r7, r0
> +     mov     r0, #0x3
> +     bl davinci_ddr_psc_config
> +     mov     r0, r7
> +
> +     /* clear  MCLKSTOPEN */
> +
> +     ldr     ip, [r0, #DDR2_SDRCR_OFFSET]
> +     bic     ip, ip, #DDR2_MCLKSTOPEN_BIT
> +     str     ip, [r0, #DDR2_SDRCR_OFFSET]
> +
> +     ldr     ip, [r0, #DDR2_SDRCR_OFFSET]
> +     bic     ip, ip, #DDR2_LPMODEN_BIT
> +     str     ip, [r0, #DDR2_SDRCR_OFFSET]
> +
> +     /* Restore registers and return */
> +     ldmfd   sp!, {r0-r12, pc}
> +
> +ENDPROC(davinci_cpu_suspend)
> +
> +/*
> + * Disables or Enables DDR2 LPSC
> + * Register Usage:
> + *   r0: Enable or Disable LPSC r0 = 0x3 => Enable, r0 = 0x2 => Disable LPSC
> + *   r1: contains virtual base for DDR2 Power and Sleep controller (PSC)
> + *   r2: contains PSC number for DDR2
> + */
> +ENTRY(davinci_ddr_psc_config)
> +     /* Set next state in mdctl for DDR2 */
> +     mov     r6, #MDCTL
> +     add     r6, r6, r2, lsl #2
> +     ldr     ip, [r1, r6]
> +     bic     ip, ip, #MDSTAT_STATE_MASK
> +     orr     ip, ip, r0
> +     str     ip, [r1, r6]
> +
> +     /* Enable the Power Domain Transition Command */
> +     ldr     ip, [r1, #PTCMD]
> +     orr     ip, ip, #0x1
> +     str     ip, [r1, #PTCMD]
> +
> +     /* Check for Transition Complete (PTSTAT) */
> +ptstat_done:
> +     ldr     ip, [r1, #PTSTAT]
> +     and     ip, ip, #0x1
> +     cmp     ip, #0x0
> +     bne     ptstat_done
> +
> +     /* Check for DDR2 clock disable completion; */
> +     mov     r6, #MDSTAT
> +     add     r6, r6, r2, lsl #2
> +ddr2clk_stop_done:
> +     ldr     ip, [r1, r6]
> +     and     ip, ip, #MDSTAT_STATE_MASK
> +     cmp     ip, r0
> +     bne     ddr2clk_stop_done
> +
> +     mov     pc, lr
> +ENDPROC(davinci_ddr_psc_config)
> +
> +CACHE_FLUSH:
> +     .word   arm926_flush_kern_cache_all
> +
> +ENTRY(davinci_cpu_suspend_sz)
> +     .word   . - davinci_cpu_suspend
> +ENDPROC(davinci_cpu_suspend_sz)
> -- 
> 1.6.2.4
>
> _______________________________________________
> Davinci-linux-open-source mailing list
> [email protected]
> http://linux.davincidsp.com/mailman/listinfo/davinci-linux-open-source
_______________________________________________
Davinci-linux-open-source mailing list
[email protected]
http://linux.davincidsp.com/mailman/listinfo/davinci-linux-open-source

Reply via email to