linux/arch/arm/mach-omap2/pm.c
<<
>>
Prefs
   1/*
   2 * pm.c - Common OMAP2+ power management-related code
   3 *
   4 * Copyright (C) 2010 Texas Instruments, Inc.
   5 * Copyright (C) 2010 Nokia Corporation
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 */
  11
  12#include <linux/kernel.h>
  13#include <linux/init.h>
  14#include <linux/io.h>
  15#include <linux/err.h>
  16#include <linux/pm_opp.h>
  17#include <linux/export.h>
  18#include <linux/suspend.h>
  19#include <linux/cpu.h>
  20
  21#include <asm/system_misc.h>
  22
  23#include "omap-pm.h"
  24#include "omap_device.h"
  25#include "common.h"
  26
  27#include "soc.h"
  28#include "prcm-common.h"
  29#include "voltage.h"
  30#include "powerdomain.h"
  31#include "clockdomain.h"
  32#include "pm.h"
  33
  34#ifdef CONFIG_SUSPEND
  35/*
  36 * omap_pm_suspend: points to a function that does the SoC-specific
  37 * suspend work
  38 */
  39static int (*omap_pm_suspend)(void);
  40#endif
  41
  42#ifdef CONFIG_PM
  43/**
  44 * struct omap2_oscillator - Describe the board main oscillator latencies
  45 * @startup_time: oscillator startup latency
  46 * @shutdown_time: oscillator shutdown latency
  47 */
  48struct omap2_oscillator {
  49        u32 startup_time;
  50        u32 shutdown_time;
  51};
  52
  53static struct omap2_oscillator oscillator = {
  54        .startup_time = ULONG_MAX,
  55        .shutdown_time = ULONG_MAX,
  56};
  57
  58void omap_pm_setup_oscillator(u32 tstart, u32 tshut)
  59{
  60        oscillator.startup_time = tstart;
  61        oscillator.shutdown_time = tshut;
  62}
  63
  64void omap_pm_get_oscillator(u32 *tstart, u32 *tshut)
  65{
  66        if (!tstart || !tshut)
  67                return;
  68
  69        *tstart = oscillator.startup_time;
  70        *tshut = oscillator.shutdown_time;
  71}
  72#endif
  73
  74int __init omap_pm_clkdms_setup(struct clockdomain *clkdm, void *unused)
  75{
  76        clkdm_allow_idle(clkdm);
  77        return 0;
  78}
  79
  80/*
  81 * This API is to be called during init to set the various voltage
  82 * domains to the voltage as per the opp table. Typically we boot up
  83 * at the nominal voltage. So this function finds out the rate of
  84 * the clock associated with the voltage domain, finds out the correct
  85 * opp entry and sets the voltage domain to the voltage specified
  86 * in the opp entry
  87 */
  88static int __init omap2_set_init_voltage(char *vdd_name, char *clk_name,
  89                                         const char *oh_name)
  90{
  91        struct voltagedomain *voltdm;
  92        struct clk *clk;
  93        struct dev_pm_opp *opp;
  94        unsigned long freq, bootup_volt;
  95        struct device *dev;
  96
  97        if (!vdd_name || !clk_name || !oh_name) {
  98                pr_err("%s: invalid parameters\n", __func__);
  99                goto exit;
 100        }
 101
 102        if (!strncmp(oh_name, "mpu", 3))
 103                /* 
 104                 * All current OMAPs share voltage rail and clock
 105                 * source, so CPU0 is used to represent the MPU-SS.
 106                 */
 107                dev = get_cpu_device(0);
 108        else
 109                dev = omap_device_get_by_hwmod_name(oh_name);
 110
 111        if (IS_ERR(dev)) {
 112                pr_err("%s: Unable to get dev pointer for hwmod %s\n",
 113                        __func__, oh_name);
 114                goto exit;
 115        }
 116
 117        voltdm = voltdm_lookup(vdd_name);
 118        if (!voltdm) {
 119                pr_err("%s: unable to get vdd pointer for vdd_%s\n",
 120                        __func__, vdd_name);
 121                goto exit;
 122        }
 123
 124        clk =  clk_get(NULL, clk_name);
 125        if (IS_ERR(clk)) {
 126                pr_err("%s: unable to get clk %s\n", __func__, clk_name);
 127                goto exit;
 128        }
 129
 130        freq = clk_get_rate(clk);
 131        clk_put(clk);
 132
 133        opp = dev_pm_opp_find_freq_ceil(dev, &freq);
 134        if (IS_ERR(opp)) {
 135                pr_err("%s: unable to find boot up OPP for vdd_%s\n",
 136                        __func__, vdd_name);
 137                goto exit;
 138        }
 139
 140        bootup_volt = dev_pm_opp_get_voltage(opp);
 141        dev_pm_opp_put(opp);
 142
 143        if (!bootup_volt) {
 144                pr_err("%s: unable to find voltage corresponding to the bootup OPP for vdd_%s\n",
 145                       __func__, vdd_name);
 146                goto exit;
 147        }
 148
 149        voltdm_scale(voltdm, bootup_volt);
 150        return 0;
 151
 152exit:
 153        pr_err("%s: unable to set vdd_%s\n", __func__, vdd_name);
 154        return -EINVAL;
 155}
 156
 157#ifdef CONFIG_SUSPEND
 158static int omap_pm_enter(suspend_state_t suspend_state)
 159{
 160        int ret = 0;
 161
 162        if (!omap_pm_suspend)
 163                return -ENOENT; /* XXX doublecheck */
 164
 165        switch (suspend_state) {
 166        case PM_SUSPEND_MEM:
 167                ret = omap_pm_suspend();
 168                break;
 169        default:
 170                ret = -EINVAL;
 171        }
 172
 173        return ret;
 174}
 175
 176static int omap_pm_begin(suspend_state_t state)
 177{
 178        cpu_idle_poll_ctrl(true);
 179        if (soc_is_omap34xx())
 180                omap_prcm_irq_prepare();
 181        return 0;
 182}
 183
 184static void omap_pm_end(void)
 185{
 186        cpu_idle_poll_ctrl(false);
 187}
 188
 189static void omap_pm_finish(void)
 190{
 191        if (soc_is_omap34xx())
 192                omap_prcm_irq_complete();
 193}
 194
 195static const struct platform_suspend_ops omap_pm_ops = {
 196        .begin          = omap_pm_begin,
 197        .end            = omap_pm_end,
 198        .enter          = omap_pm_enter,
 199        .finish         = omap_pm_finish,
 200        .valid          = suspend_valid_only_mem,
 201};
 202
 203/**
 204 * omap_common_suspend_init - Set common suspend routines for OMAP SoCs
 205 * @pm_suspend: function pointer to SoC specific suspend function
 206 */
 207void omap_common_suspend_init(void *pm_suspend)
 208{
 209        omap_pm_suspend = pm_suspend;
 210        suspend_set_ops(&omap_pm_ops);
 211}
 212#endif /* CONFIG_SUSPEND */
 213
 214static void __init omap3_init_voltages(void)
 215{
 216        if (!soc_is_omap34xx())
 217                return;
 218
 219        omap2_set_init_voltage("mpu_iva", "dpll1_ck", "mpu");
 220        omap2_set_init_voltage("core", "l3_ick", "l3_main");
 221}
 222
 223static void __init omap4_init_voltages(void)
 224{
 225        if (!soc_is_omap44xx())
 226                return;
 227
 228        omap2_set_init_voltage("mpu", "dpll_mpu_ck", "mpu");
 229        omap2_set_init_voltage("core", "l3_div_ck", "l3_main_1");
 230        omap2_set_init_voltage("iva", "dpll_iva_m5x2_ck", "iva");
 231}
 232
 233static int __init omap2_common_pm_init(void)
 234{
 235        omap_pm_if_init();
 236
 237        return 0;
 238}
 239omap_postcore_initcall(omap2_common_pm_init);
 240
 241int __init omap2_common_pm_late_init(void)
 242{
 243        /* Init the voltage layer */
 244        omap3_twl_init();
 245        omap4_twl_init();
 246        omap_voltage_late_init();
 247
 248        /* Initialize the voltages */
 249        omap3_init_voltages();
 250        omap4_init_voltages();
 251
 252        /* Smartreflex device init */
 253        omap_devinit_smartreflex();
 254
 255        return 0;
 256}
 257