Skip to content

Commit

Permalink
davinci: add power management support
Browse files Browse the repository at this point in the history
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 <nsekhar@ti.com>
Signed-off-by: Kevin Hilman <khilman@deeprootsystems.com>
  • Loading branch information
Sekhar Nori authored and Kevin Hilman committed Feb 4, 2010
1 parent 7307d09 commit efc1bb8
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 0 deletions.
1 change: 1 addition & 0 deletions arch/arm/mach-davinci/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions arch/arm/mach-davinci/include/mach/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

/*
Expand Down
54 changes: 54 additions & 0 deletions arch/arm/mach-davinci/include/mach/pm.h
Original file line number Diff line number Diff line change
@@ -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
158 changes: 158 additions & 0 deletions arch/arm/mach-davinci/pm.c
Original file line number Diff line number Diff line change
@@ -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);
Loading

0 comments on commit efc1bb8

Please sign in to comment.