mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-26 06:04:14 +08:00
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>
340 lines
8.6 KiB
C
340 lines
8.6 KiB
C
/*
|
|
* OMAP2 and OMAP3 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/types.h>
|
|
#include <plat/prcm.h>
|
|
#include "prm.h"
|
|
#include "prm2xxx_3xxx.h"
|
|
#include "cm.h"
|
|
#include "cm2xxx_3xxx.h"
|
|
#include "cm-regbits-24xx.h"
|
|
#include "cm-regbits-34xx.h"
|
|
#include "prm-regbits-24xx.h"
|
|
#include "clockdomain.h"
|
|
|
|
static int omap2_clkdm_add_wkdep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
omap2_prm_set_mod_reg_bits((1 << clkdm2->dep_bit),
|
|
clkdm1->pwrdm.ptr->prcm_offs, PM_WKDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap2_clkdm_del_wkdep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
omap2_prm_clear_mod_reg_bits((1 << clkdm2->dep_bit),
|
|
clkdm1->pwrdm.ptr->prcm_offs, PM_WKDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap2_clkdm_read_wkdep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
return omap2_prm_read_mod_bits_shift(clkdm1->pwrdm.ptr->prcm_offs,
|
|
PM_WKDEP, (1 << clkdm2->dep_bit));
|
|
}
|
|
|
|
static int omap2_clkdm_clear_all_wkdeps(struct clockdomain *clkdm)
|
|
{
|
|
struct clkdm_dep *cd;
|
|
u32 mask = 0;
|
|
|
|
for (cd = clkdm->wkdep_srcs; cd && cd->clkdm_name; cd++) {
|
|
if (!cd->clkdm)
|
|
continue; /* only happens if data is erroneous */
|
|
|
|
/* PRM accesses are slow, so minimize them */
|
|
mask |= 1 << cd->clkdm->dep_bit;
|
|
atomic_set(&cd->wkdep_usecount, 0);
|
|
}
|
|
|
|
omap2_prm_clear_mod_reg_bits(mask, clkdm->pwrdm.ptr->prcm_offs,
|
|
PM_WKDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap3_clkdm_add_sleepdep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
omap2_cm_set_mod_reg_bits((1 << clkdm2->dep_bit),
|
|
clkdm1->pwrdm.ptr->prcm_offs,
|
|
OMAP3430_CM_SLEEPDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap3_clkdm_del_sleepdep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
omap2_cm_clear_mod_reg_bits((1 << clkdm2->dep_bit),
|
|
clkdm1->pwrdm.ptr->prcm_offs,
|
|
OMAP3430_CM_SLEEPDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap3_clkdm_read_sleepdep(struct clockdomain *clkdm1,
|
|
struct clockdomain *clkdm2)
|
|
{
|
|
return omap2_prm_read_mod_bits_shift(clkdm1->pwrdm.ptr->prcm_offs,
|
|
OMAP3430_CM_SLEEPDEP, (1 << clkdm2->dep_bit));
|
|
}
|
|
|
|
static int omap3_clkdm_clear_all_sleepdeps(struct clockdomain *clkdm)
|
|
{
|
|
struct clkdm_dep *cd;
|
|
u32 mask = 0;
|
|
|
|
for (cd = clkdm->sleepdep_srcs; cd && cd->clkdm_name; cd++) {
|
|
if (!cd->clkdm)
|
|
continue; /* only happens if data is erroneous */
|
|
|
|
/* PRM accesses are slow, so minimize them */
|
|
mask |= 1 << cd->clkdm->dep_bit;
|
|
atomic_set(&cd->sleepdep_usecount, 0);
|
|
}
|
|
omap2_prm_clear_mod_reg_bits(mask, clkdm->pwrdm.ptr->prcm_offs,
|
|
OMAP3430_CM_SLEEPDEP);
|
|
return 0;
|
|
}
|
|
|
|
static int omap2_clkdm_sleep(struct clockdomain *clkdm)
|
|
{
|
|
omap2_cm_set_mod_reg_bits(OMAP24XX_FORCESTATE_MASK,
|
|
clkdm->pwrdm.ptr->prcm_offs,
|
|
OMAP2_PM_PWSTCTRL);
|
|
return 0;
|
|
}
|
|
|
|
static int omap2_clkdm_wakeup(struct clockdomain *clkdm)
|
|
{
|
|
omap2_cm_clear_mod_reg_bits(OMAP24XX_FORCESTATE_MASK,
|
|
clkdm->pwrdm.ptr->prcm_offs,
|
|
OMAP2_PM_PWSTCTRL);
|
|
return 0;
|
|
}
|
|
|
|
static void omap2_clkdm_allow_idle(struct clockdomain *clkdm)
|
|
{
|
|
if (atomic_read(&clkdm->usecount) > 0)
|
|
_clkdm_add_autodeps(clkdm);
|
|
|
|
omap2xxx_cm_clkdm_enable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
}
|
|
|
|
static void omap2_clkdm_deny_idle(struct clockdomain *clkdm)
|
|
{
|
|
omap2xxx_cm_clkdm_disable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
|
|
if (atomic_read(&clkdm->usecount) > 0)
|
|
_clkdm_del_autodeps(clkdm);
|
|
}
|
|
|
|
static void _enable_hwsup(struct clockdomain *clkdm)
|
|
{
|
|
if (cpu_is_omap24xx())
|
|
omap2xxx_cm_clkdm_enable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
else if (cpu_is_omap34xx())
|
|
omap3xxx_cm_clkdm_enable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
}
|
|
|
|
static void _disable_hwsup(struct clockdomain *clkdm)
|
|
{
|
|
if (cpu_is_omap24xx())
|
|
omap2xxx_cm_clkdm_disable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
else if (cpu_is_omap34xx())
|
|
omap3xxx_cm_clkdm_disable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
}
|
|
|
|
static int omap3_clkdm_sleep(struct clockdomain *clkdm)
|
|
{
|
|
omap3xxx_cm_clkdm_force_sleep(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
return 0;
|
|
}
|
|
|
|
static int omap3_clkdm_wakeup(struct clockdomain *clkdm)
|
|
{
|
|
omap3xxx_cm_clkdm_force_wakeup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
return 0;
|
|
}
|
|
|
|
static int omap2_clkdm_clk_enable(struct clockdomain *clkdm)
|
|
{
|
|
bool hwsup = false;
|
|
|
|
if (!clkdm->clktrctrl_mask)
|
|
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)) {
|
|
_enable_hwsup(clkdm);
|
|
return 0;
|
|
}
|
|
|
|
hwsup = omap2_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
|
|
if (hwsup) {
|
|
/* Disable HW transitions when we are changing deps */
|
|
_disable_hwsup(clkdm);
|
|
_clkdm_add_autodeps(clkdm);
|
|
_enable_hwsup(clkdm);
|
|
} else {
|
|
if (clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)
|
|
omap2_clkdm_wakeup(clkdm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap2_clkdm_clk_disable(struct clockdomain *clkdm)
|
|
{
|
|
bool hwsup = false;
|
|
|
|
if (!clkdm->clktrctrl_mask)
|
|
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_WAKEUP)) {
|
|
omap3_clkdm_wakeup(clkdm);
|
|
return 0;
|
|
}
|
|
|
|
hwsup = omap2_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
|
|
if (hwsup) {
|
|
/* Disable HW transitions when we are changing deps */
|
|
_disable_hwsup(clkdm);
|
|
_clkdm_del_autodeps(clkdm);
|
|
_enable_hwsup(clkdm);
|
|
} else {
|
|
if (clkdm->flags & CLKDM_CAN_FORCE_SLEEP)
|
|
omap2_clkdm_sleep(clkdm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void omap3_clkdm_allow_idle(struct clockdomain *clkdm)
|
|
{
|
|
if (atomic_read(&clkdm->usecount) > 0)
|
|
_clkdm_add_autodeps(clkdm);
|
|
|
|
omap3xxx_cm_clkdm_enable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
}
|
|
|
|
static void omap3_clkdm_deny_idle(struct clockdomain *clkdm)
|
|
{
|
|
omap3xxx_cm_clkdm_disable_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
|
|
if (atomic_read(&clkdm->usecount) > 0)
|
|
_clkdm_del_autodeps(clkdm);
|
|
}
|
|
|
|
static int omap3xxx_clkdm_clk_enable(struct clockdomain *clkdm)
|
|
{
|
|
bool hwsup = false;
|
|
|
|
if (!clkdm->clktrctrl_mask)
|
|
return 0;
|
|
|
|
hwsup = omap2_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
|
|
if (hwsup) {
|
|
/* Disable HW transitions when we are changing deps */
|
|
_disable_hwsup(clkdm);
|
|
_clkdm_add_autodeps(clkdm);
|
|
_enable_hwsup(clkdm);
|
|
} else {
|
|
if (clkdm->flags & CLKDM_CAN_FORCE_WAKEUP)
|
|
omap3_clkdm_wakeup(clkdm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int omap3xxx_clkdm_clk_disable(struct clockdomain *clkdm)
|
|
{
|
|
bool hwsup = false;
|
|
|
|
if (!clkdm->clktrctrl_mask)
|
|
return 0;
|
|
|
|
hwsup = omap2_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs,
|
|
clkdm->clktrctrl_mask);
|
|
|
|
if (hwsup) {
|
|
/* Disable HW transitions when we are changing deps */
|
|
_disable_hwsup(clkdm);
|
|
_clkdm_del_autodeps(clkdm);
|
|
_enable_hwsup(clkdm);
|
|
} else {
|
|
if (clkdm->flags & CLKDM_CAN_FORCE_SLEEP)
|
|
omap3_clkdm_sleep(clkdm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct clkdm_ops omap2_clkdm_operations = {
|
|
.clkdm_add_wkdep = omap2_clkdm_add_wkdep,
|
|
.clkdm_del_wkdep = omap2_clkdm_del_wkdep,
|
|
.clkdm_read_wkdep = omap2_clkdm_read_wkdep,
|
|
.clkdm_clear_all_wkdeps = omap2_clkdm_clear_all_wkdeps,
|
|
.clkdm_sleep = omap2_clkdm_sleep,
|
|
.clkdm_wakeup = omap2_clkdm_wakeup,
|
|
.clkdm_allow_idle = omap2_clkdm_allow_idle,
|
|
.clkdm_deny_idle = omap2_clkdm_deny_idle,
|
|
.clkdm_clk_enable = omap2_clkdm_clk_enable,
|
|
.clkdm_clk_disable = omap2_clkdm_clk_disable,
|
|
};
|
|
|
|
struct clkdm_ops omap3_clkdm_operations = {
|
|
.clkdm_add_wkdep = omap2_clkdm_add_wkdep,
|
|
.clkdm_del_wkdep = omap2_clkdm_del_wkdep,
|
|
.clkdm_read_wkdep = omap2_clkdm_read_wkdep,
|
|
.clkdm_clear_all_wkdeps = omap2_clkdm_clear_all_wkdeps,
|
|
.clkdm_add_sleepdep = omap3_clkdm_add_sleepdep,
|
|
.clkdm_del_sleepdep = omap3_clkdm_del_sleepdep,
|
|
.clkdm_read_sleepdep = omap3_clkdm_read_sleepdep,
|
|
.clkdm_clear_all_sleepdeps = omap3_clkdm_clear_all_sleepdeps,
|
|
.clkdm_sleep = omap3_clkdm_sleep,
|
|
.clkdm_wakeup = omap3_clkdm_wakeup,
|
|
.clkdm_allow_idle = omap3_clkdm_allow_idle,
|
|
.clkdm_deny_idle = omap3_clkdm_deny_idle,
|
|
.clkdm_clk_enable = omap3xxx_clkdm_clk_enable,
|
|
.clkdm_clk_disable = omap3xxx_clkdm_clk_disable,
|
|
};
|