b71c72178e
The idle status of the IP blocks and clocks inside the EMU clockdomain isn't taken into account by the PRCM hardware when deciding whether the clockdomain is idle. Add a workaround flag in the clockdomain code, CLKDM_MISSING_IDLE_REPORTING, to deal with this problem, and add the code necessary to support it. If CLKDM_MISSING_IDLE_REPORTING is set on a clockdomain, the clockdomain will be forced active whenever an IP block inside that clockdomain is in use, even if the clockdomain supports hardware-supervised idle. When the kernel indicates that the last active IP block inside the clockdomain is no longer used, the clockdomain will be forced idle, or, if that mode is not supported in the hardware, it will be placed into hardware-supervised idle. This patch is an equal collaboration with Jon Hunter <jon-hunter@ti.com>. Ming Lei <ming.lei@canonical.com>, Will Deacon <will.deacon@arm.com>, Madhav Vij <mvij@ti.com>, Kevin Hilman <khilman@ti.com>, Benoît Cousson <b-cousson@ti.com>, and Santosh Shilimkar <santosh.shilimkar@ti.com> all made essential contributions to the understanding of EMU clockdomain power management on OMAP. Signed-off-by: Paul Walmsley <paul@pwsan.com> Cc: Jon Hunter <jon-hunter@ti.com> Cc: Ming Lei <ming.lei@canonical.com> Cc: Will Deacon <will.deacon@arm.com> Cc: Madhav Vij <mvij@ti.com> Cc: Kevin Hilman <khilman@ti.com> Cc: Benoît Cousson <b-cousson@ti.com> Cc: Santosh Shilimkar <santosh.shilimkar@ti.com> Tested-by: Jon Hunter <jon-hunter@ti.com>
152 lines
4.1 KiB
C
152 lines
4.1 KiB
C
/*
|
|
* OMAP4 clockdomain control
|
|
*
|
|
* Copyright (C) 2008-2010 Texas Instruments, Inc.
|
|
* Copyright (C) 2008-2010 Nokia Corporation
|
|
*
|
|
* Derived from mach-omap2/clockdomain.c written by Paul Walmsley
|
|
* Rajendra Nayak <rnayak@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/kernel.h>
|
|
#include "clockdomain.h"
|
|
#include "cminst44xx.h"
|
|
#include "cm44xx.h"
|
|
|
|
static int omap4_clkdm_add_wkup_sleep_dep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
omap4_cminst_set_inst_reg_bits((1 << clkdm2->dep_bit),
|
|
clkdm1->prcm_partition,
|
|
clkdm1->cm_inst, clkdm1->clkdm_offs +
|
|
OMAP4_CM_STATICDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap4_clkdm_del_wkup_sleep_dep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
omap4_cminst_clear_inst_reg_bits((1 << clkdm2->dep_bit),
|
|
clkdm1->prcm_partition,
|
|
clkdm1->cm_inst, clkdm1->clkdm_offs +
|
|
OMAP4_CM_STATICDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap4_clkdm_read_wkup_sleep_dep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
return omap4_cminst_read_inst_reg_bits(clkdm1->prcm_partition,
|
|
clkdm1->cm_inst, clkdm1->clkdm_offs +
|
|
OMAP4_CM_STATICDEP,
|
|
(1 << clkdm2->dep_bit));
|
|
}
|
|
|
|
static int omap4_clkdm_clear_all_wkup_sleep_deps(struct clockdomain *clkdm)
|
|
{
|
|
struct clkdm_dep *cd;
|
|
u32 mask = 0;
|
|
|
|
if (!clkdm->prcm_partition)
|
|
return 0;
|
|
|
|
for (cd = clkdm->wkdep_srcs; cd && cd->clkdm_name; cd++) {
|
|
if (!cd->clkdm)
|
|
continue; /* only happens if data is erroneous */
|
|
|
|
mask |= 1 << cd->clkdm->dep_bit;
|
|
atomic_set(&cd->wkdep_usecount, 0);
|
|
}
|
|
|
|
omap4_cminst_clear_inst_reg_bits(mask, clkdm->prcm_partition,
|
|
clkdm->cm_inst, clkdm->clkdm_offs +
|
|
OMAP4_CM_STATICDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap4_clkdm_sleep(struct clockdomain *clkdm)
|
|
{
|
|
omap4_cminst_clkdm_enable_hwsup(clkdm->prcm_partition,
|
|
clkdm->cm_inst, clkdm->clkdm_offs);
|
|
return 0;
|
|
}
|
|
|
|
static int omap4_clkdm_wakeup(struct clockdomain *clkdm)
|
|
{
|
|
omap4_cminst_clkdm_force_wakeup(clkdm->prcm_partition,
|
|
clkdm->cm_inst, clkdm->clkdm_offs);
|
|
return 0;
|
|
}
|
|
|
|
static void omap4_clkdm_allow_idle(struct clockdomain *clkdm)
|
|
{
|
|
omap4_cminst_clkdm_enable_hwsup(clkdm->prcm_partition,
|
|
clkdm->cm_inst, clkdm->clkdm_offs);
|
|
}
|
|
|
|
static void omap4_clkdm_deny_idle(struct clockdomain *clkdm)
|
|
{
|
|
if (clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)
|
|
omap4_clkdm_wakeup(clkdm);
|
|
else
|
|
omap4_cminst_clkdm_disable_hwsup(clkdm->prcm_partition,
|
|
clkdm->cm_inst,
|
|
clkdm->clkdm_offs);
|
|
}
|
|
|
|
static int omap4_clkdm_clk_enable(struct clockdomain *clkdm)
|
|
{
|
|
if (clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)
|
|
return omap4_clkdm_wakeup(clkdm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap4_clkdm_clk_disable(struct clockdomain *clkdm)
|
|
{
|
|
bool hwsup = false;
|
|
|
|
if (!clkdm->prcm_partition)
|
|
return 0;
|
|
|
|
/*
|
|
* The CLKDM_MISSING_IDLE_REPORTING flag documentation has
|
|
* more details on the unpleasant problem this is working
|
|
* around
|
|
*/
|
|
if (clkdm->flags & CLKDM_MISSING_IDLE_REPORTING &&
|
|
!(clkdm->flags & CLKDM_CAN_FORCE_SLEEP)) {
|
|
omap4_clkdm_allow_idle(clkdm);
|
|
return 0;
|
|
}
|
|
|
|
hwsup = omap4_cminst_is_clkdm_in_hwsup(clkdm->prcm_partition,
|
|
clkdm->cm_inst, clkdm->clkdm_offs);
|
|
|
|
if (!hwsup && (clkdm->flags & CLKDM_CAN_FORCE_SLEEP))
|
|
omap4_clkdm_sleep(clkdm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct clkdm_ops omap4_clkdm_operations = {
|
|
.clkdm_add_wkdep = omap4_clkdm_add_wkup_sleep_dep,
|
|
.clkdm_del_wkdep = omap4_clkdm_del_wkup_sleep_dep,
|
|
.clkdm_read_wkdep = omap4_clkdm_read_wkup_sleep_dep,
|
|
.clkdm_clear_all_wkdeps = omap4_clkdm_clear_all_wkup_sleep_deps,
|
|
.clkdm_add_sleepdep = omap4_clkdm_add_wkup_sleep_dep,
|
|
.clkdm_del_sleepdep = omap4_clkdm_del_wkup_sleep_dep,
|
|
.clkdm_read_sleepdep = omap4_clkdm_read_wkup_sleep_dep,
|
|
.clkdm_clear_all_sleepdeps = omap4_clkdm_clear_all_wkup_sleep_deps,
|
|
.clkdm_sleep = omap4_clkdm_sleep,
|
|
.clkdm_wakeup = omap4_clkdm_wakeup,
|
|
.clkdm_allow_idle = omap4_clkdm_allow_idle,
|
|
.clkdm_deny_idle = omap4_clkdm_deny_idle,
|
|
.clkdm_clk_enable = omap4_clkdm_clk_enable,
|
|
.clkdm_clk_disable = omap4_clkdm_clk_disable,
|
|
};
|