forked from Minki/linux
485995b09b
The powerdomain control registers are stored in the WKUP powerdomain on AM33XX/AM43XX, which is lost on RTC-only suspend and also hibernate. This adds context save and restore functions for those registers. Sometimes the powerdomain state does not need to change, perhaps we only need to change memory retention states, so make sure the restored state is different from the current state before we wait for a transition. Signed-off-by: Keerthy <j-keerthy@ti.com> Signed-off-by: Dave Gerlach <d-gerlach@ti.com> Signed-off-by: Russ Dill <Russ.Dill@ti.com> Signed-off-by: Tony Lindgren <tony@atomide.com>
410 lines
11 KiB
C
410 lines
11 KiB
C
/*
|
|
* AM33XX PRM functions
|
|
*
|
|
* Copyright (C) 2011-2012 Texas Instruments Incorporated - 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.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
|
|
#include "powerdomain.h"
|
|
#include "prm33xx.h"
|
|
#include "prm-regbits-33xx.h"
|
|
|
|
#define AM33XX_PRM_RSTCTRL_OFFSET 0x0000
|
|
|
|
#define AM33XX_RST_GLOBAL_WARM_SW_MASK (1 << 0)
|
|
|
|
/* Read a register in a PRM instance */
|
|
static u32 am33xx_prm_read_reg(s16 inst, u16 idx)
|
|
{
|
|
return readl_relaxed(prm_base.va + inst + idx);
|
|
}
|
|
|
|
/* Write into a register in a PRM instance */
|
|
static void am33xx_prm_write_reg(u32 val, s16 inst, u16 idx)
|
|
{
|
|
writel_relaxed(val, prm_base.va + inst + idx);
|
|
}
|
|
|
|
/* Read-modify-write a register in PRM. Caller must lock */
|
|
static u32 am33xx_prm_rmw_reg_bits(u32 mask, u32 bits, s16 inst, s16 idx)
|
|
{
|
|
u32 v;
|
|
|
|
v = am33xx_prm_read_reg(inst, idx);
|
|
v &= ~mask;
|
|
v |= bits;
|
|
am33xx_prm_write_reg(v, inst, idx);
|
|
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* am33xx_prm_is_hardreset_asserted - read the HW reset line state of
|
|
* submodules contained in the hwmod module
|
|
* @shift: register bit shift corresponding to the reset line to check
|
|
* @part: PRM partition, ignored for AM33xx
|
|
* @inst: CM instance register offset (*_INST macro)
|
|
* @rstctrl_offs: RM_RSTCTRL register address offset for this module
|
|
*
|
|
* Returns 1 if the (sub)module hardreset line is currently asserted,
|
|
* 0 if the (sub)module hardreset line is not currently asserted, or
|
|
* -EINVAL upon parameter error.
|
|
*/
|
|
static int am33xx_prm_is_hardreset_asserted(u8 shift, u8 part, s16 inst,
|
|
u16 rstctrl_offs)
|
|
{
|
|
u32 v;
|
|
|
|
v = am33xx_prm_read_reg(inst, rstctrl_offs);
|
|
v &= 1 << shift;
|
|
v >>= shift;
|
|
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* am33xx_prm_assert_hardreset - assert the HW reset line of a submodule
|
|
* @shift: register bit shift corresponding to the reset line to assert
|
|
* @part: CM partition, ignored for AM33xx
|
|
* @inst: CM instance register offset (*_INST macro)
|
|
* @rstctrl_reg: RM_RSTCTRL register address for this module
|
|
*
|
|
* Some IPs like dsp, ipu or iva contain processors that require an HW
|
|
* reset line to be asserted / deasserted in order to fully enable the
|
|
* IP. These modules may have multiple hard-reset lines that reset
|
|
* different 'submodules' inside the IP block. This function will
|
|
* place the submodule into reset. Returns 0 upon success or -EINVAL
|
|
* upon an argument error.
|
|
*/
|
|
static int am33xx_prm_assert_hardreset(u8 shift, u8 part, s16 inst,
|
|
u16 rstctrl_offs)
|
|
{
|
|
u32 mask = 1 << shift;
|
|
|
|
am33xx_prm_rmw_reg_bits(mask, mask, inst, rstctrl_offs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* am33xx_prm_deassert_hardreset - deassert a submodule hardreset line and
|
|
* wait
|
|
* @shift: register bit shift corresponding to the reset line to deassert
|
|
* @st_shift: reset status register bit shift corresponding to the reset line
|
|
* @part: PRM partition, not used for AM33xx
|
|
* @inst: CM instance register offset (*_INST macro)
|
|
* @rstctrl_reg: RM_RSTCTRL register address for this module
|
|
* @rstst_reg: RM_RSTST register address for this module
|
|
*
|
|
* Some IPs like dsp, ipu or iva contain processors that require an HW
|
|
* reset line to be asserted / deasserted in order to fully enable the
|
|
* IP. These modules may have multiple hard-reset lines that reset
|
|
* different 'submodules' inside the IP block. This function will
|
|
* take the submodule out of reset and wait until the PRCM indicates
|
|
* that the reset has completed before returning. Returns 0 upon success or
|
|
* -EINVAL upon an argument error, -EEXIST if the submodule was already out
|
|
* of reset, or -EBUSY if the submodule did not exit reset promptly.
|
|
*/
|
|
static int am33xx_prm_deassert_hardreset(u8 shift, u8 st_shift, u8 part,
|
|
s16 inst, u16 rstctrl_offs,
|
|
u16 rstst_offs)
|
|
{
|
|
int c;
|
|
u32 mask = 1 << st_shift;
|
|
|
|
/* Check the current status to avoid de-asserting the line twice */
|
|
if (am33xx_prm_is_hardreset_asserted(shift, 0, inst, rstctrl_offs) == 0)
|
|
return -EEXIST;
|
|
|
|
/* Clear the reset status by writing 1 to the status bit */
|
|
am33xx_prm_rmw_reg_bits(0xffffffff, mask, inst, rstst_offs);
|
|
|
|
/* de-assert the reset control line */
|
|
mask = 1 << shift;
|
|
|
|
am33xx_prm_rmw_reg_bits(mask, 0, inst, rstctrl_offs);
|
|
|
|
/* wait the status to be set */
|
|
omap_test_timeout(am33xx_prm_is_hardreset_asserted(st_shift, 0, inst,
|
|
rstst_offs),
|
|
MAX_MODULE_HARDRESET_WAIT, c);
|
|
|
|
return (c == MAX_MODULE_HARDRESET_WAIT) ? -EBUSY : 0;
|
|
}
|
|
|
|
static int am33xx_pwrdm_set_next_pwrst(struct powerdomain *pwrdm, u8 pwrst)
|
|
{
|
|
am33xx_prm_rmw_reg_bits(OMAP_POWERSTATE_MASK,
|
|
(pwrst << OMAP_POWERSTATE_SHIFT),
|
|
pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
return 0;
|
|
}
|
|
|
|
static int am33xx_pwrdm_read_next_pwrst(struct powerdomain *pwrdm)
|
|
{
|
|
u32 v;
|
|
|
|
v = am33xx_prm_read_reg(pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
v &= OMAP_POWERSTATE_MASK;
|
|
v >>= OMAP_POWERSTATE_SHIFT;
|
|
|
|
return v;
|
|
}
|
|
|
|
static int am33xx_pwrdm_read_pwrst(struct powerdomain *pwrdm)
|
|
{
|
|
u32 v;
|
|
|
|
v = am33xx_prm_read_reg(pwrdm->prcm_offs, pwrdm->pwrstst_offs);
|
|
v &= OMAP_POWERSTATEST_MASK;
|
|
v >>= OMAP_POWERSTATEST_SHIFT;
|
|
|
|
return v;
|
|
}
|
|
|
|
static int am33xx_pwrdm_set_lowpwrstchange(struct powerdomain *pwrdm)
|
|
{
|
|
am33xx_prm_rmw_reg_bits(AM33XX_LOWPOWERSTATECHANGE_MASK,
|
|
(1 << AM33XX_LOWPOWERSTATECHANGE_SHIFT),
|
|
pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
return 0;
|
|
}
|
|
|
|
static int am33xx_pwrdm_clear_all_prev_pwrst(struct powerdomain *pwrdm)
|
|
{
|
|
am33xx_prm_rmw_reg_bits(AM33XX_LASTPOWERSTATEENTERED_MASK,
|
|
AM33XX_LASTPOWERSTATEENTERED_MASK,
|
|
pwrdm->prcm_offs, pwrdm->pwrstst_offs);
|
|
return 0;
|
|
}
|
|
|
|
static int am33xx_pwrdm_set_logic_retst(struct powerdomain *pwrdm, u8 pwrst)
|
|
{
|
|
u32 m;
|
|
|
|
m = pwrdm->logicretstate_mask;
|
|
if (!m)
|
|
return -EINVAL;
|
|
|
|
am33xx_prm_rmw_reg_bits(m, (pwrst << __ffs(m)),
|
|
pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int am33xx_pwrdm_read_logic_pwrst(struct powerdomain *pwrdm)
|
|
{
|
|
u32 v;
|
|
|
|
v = am33xx_prm_read_reg(pwrdm->prcm_offs, pwrdm->pwrstst_offs);
|
|
v &= AM33XX_LOGICSTATEST_MASK;
|
|
v >>= AM33XX_LOGICSTATEST_SHIFT;
|
|
|
|
return v;
|
|
}
|
|
|
|
static int am33xx_pwrdm_read_logic_retst(struct powerdomain *pwrdm)
|
|
{
|
|
u32 v, m;
|
|
|
|
m = pwrdm->logicretstate_mask;
|
|
if (!m)
|
|
return -EINVAL;
|
|
|
|
v = am33xx_prm_read_reg(pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
v &= m;
|
|
v >>= __ffs(m);
|
|
|
|
return v;
|
|
}
|
|
|
|
static int am33xx_pwrdm_set_mem_onst(struct powerdomain *pwrdm, u8 bank,
|
|
u8 pwrst)
|
|
{
|
|
u32 m;
|
|
|
|
m = pwrdm->mem_on_mask[bank];
|
|
if (!m)
|
|
return -EINVAL;
|
|
|
|
am33xx_prm_rmw_reg_bits(m, (pwrst << __ffs(m)),
|
|
pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int am33xx_pwrdm_set_mem_retst(struct powerdomain *pwrdm, u8 bank,
|
|
u8 pwrst)
|
|
{
|
|
u32 m;
|
|
|
|
m = pwrdm->mem_ret_mask[bank];
|
|
if (!m)
|
|
return -EINVAL;
|
|
|
|
am33xx_prm_rmw_reg_bits(m, (pwrst << __ffs(m)),
|
|
pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int am33xx_pwrdm_read_mem_pwrst(struct powerdomain *pwrdm, u8 bank)
|
|
{
|
|
u32 m, v;
|
|
|
|
m = pwrdm->mem_pwrst_mask[bank];
|
|
if (!m)
|
|
return -EINVAL;
|
|
|
|
v = am33xx_prm_read_reg(pwrdm->prcm_offs, pwrdm->pwrstst_offs);
|
|
v &= m;
|
|
v >>= __ffs(m);
|
|
|
|
return v;
|
|
}
|
|
|
|
static int am33xx_pwrdm_read_mem_retst(struct powerdomain *pwrdm, u8 bank)
|
|
{
|
|
u32 m, v;
|
|
|
|
m = pwrdm->mem_retst_mask[bank];
|
|
if (!m)
|
|
return -EINVAL;
|
|
|
|
v = am33xx_prm_read_reg(pwrdm->prcm_offs, pwrdm->pwrstctrl_offs);
|
|
v &= m;
|
|
v >>= __ffs(m);
|
|
|
|
return v;
|
|
}
|
|
|
|
static int am33xx_pwrdm_wait_transition(struct powerdomain *pwrdm)
|
|
{
|
|
u32 c = 0;
|
|
|
|
/*
|
|
* REVISIT: pwrdm_wait_transition() may be better implemented
|
|
* via a callback and a periodic timer check -- how long do we expect
|
|
* powerdomain transitions to take?
|
|
*/
|
|
|
|
/* XXX Is this udelay() value meaningful? */
|
|
while ((am33xx_prm_read_reg(pwrdm->prcm_offs, pwrdm->pwrstst_offs)
|
|
& OMAP_INTRANSITION_MASK) &&
|
|
(c++ < PWRDM_TRANSITION_BAILOUT))
|
|
udelay(1);
|
|
|
|
if (c > PWRDM_TRANSITION_BAILOUT) {
|
|
pr_err("powerdomain: %s: waited too long to complete transition\n",
|
|
pwrdm->name);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
pr_debug("powerdomain: completed transition in %d loops\n", c);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int am33xx_check_vcvp(void)
|
|
{
|
|
/* No VC/VP on am33xx devices */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* am33xx_prm_global_warm_sw_reset - reboot the device via warm reset
|
|
*
|
|
* Immediately reboots the device through warm reset.
|
|
*/
|
|
static void am33xx_prm_global_warm_sw_reset(void)
|
|
{
|
|
am33xx_prm_rmw_reg_bits(AM33XX_RST_GLOBAL_WARM_SW_MASK,
|
|
AM33XX_RST_GLOBAL_WARM_SW_MASK,
|
|
AM33XX_PRM_DEVICE_MOD,
|
|
AM33XX_PRM_RSTCTRL_OFFSET);
|
|
|
|
/* OCP barrier */
|
|
(void)am33xx_prm_read_reg(AM33XX_PRM_DEVICE_MOD,
|
|
AM33XX_PRM_RSTCTRL_OFFSET);
|
|
}
|
|
|
|
static void am33xx_pwrdm_save_context(struct powerdomain *pwrdm)
|
|
{
|
|
pwrdm->context = am33xx_prm_read_reg(pwrdm->prcm_offs,
|
|
pwrdm->pwrstctrl_offs);
|
|
/*
|
|
* Do not save LOWPOWERSTATECHANGE, writing a 1 indicates a request,
|
|
* reading back a 1 indicates a request in progress.
|
|
*/
|
|
pwrdm->context &= ~AM33XX_LOWPOWERSTATECHANGE_MASK;
|
|
}
|
|
|
|
static void am33xx_pwrdm_restore_context(struct powerdomain *pwrdm)
|
|
{
|
|
int st, ctrl;
|
|
|
|
st = am33xx_prm_read_reg(pwrdm->prcm_offs,
|
|
pwrdm->pwrstst_offs);
|
|
|
|
am33xx_prm_write_reg(pwrdm->context, pwrdm->prcm_offs,
|
|
pwrdm->pwrstctrl_offs);
|
|
|
|
/* Make sure we only wait for a transition if there is one */
|
|
st &= OMAP_POWERSTATEST_MASK;
|
|
ctrl = OMAP_POWERSTATEST_MASK & pwrdm->context;
|
|
|
|
if (st != ctrl)
|
|
am33xx_pwrdm_wait_transition(pwrdm);
|
|
}
|
|
|
|
struct pwrdm_ops am33xx_pwrdm_operations = {
|
|
.pwrdm_set_next_pwrst = am33xx_pwrdm_set_next_pwrst,
|
|
.pwrdm_read_next_pwrst = am33xx_pwrdm_read_next_pwrst,
|
|
.pwrdm_read_pwrst = am33xx_pwrdm_read_pwrst,
|
|
.pwrdm_set_logic_retst = am33xx_pwrdm_set_logic_retst,
|
|
.pwrdm_read_logic_pwrst = am33xx_pwrdm_read_logic_pwrst,
|
|
.pwrdm_read_logic_retst = am33xx_pwrdm_read_logic_retst,
|
|
.pwrdm_clear_all_prev_pwrst = am33xx_pwrdm_clear_all_prev_pwrst,
|
|
.pwrdm_set_lowpwrstchange = am33xx_pwrdm_set_lowpwrstchange,
|
|
.pwrdm_read_mem_pwrst = am33xx_pwrdm_read_mem_pwrst,
|
|
.pwrdm_read_mem_retst = am33xx_pwrdm_read_mem_retst,
|
|
.pwrdm_set_mem_onst = am33xx_pwrdm_set_mem_onst,
|
|
.pwrdm_set_mem_retst = am33xx_pwrdm_set_mem_retst,
|
|
.pwrdm_wait_transition = am33xx_pwrdm_wait_transition,
|
|
.pwrdm_has_voltdm = am33xx_check_vcvp,
|
|
.pwrdm_save_context = am33xx_pwrdm_save_context,
|
|
.pwrdm_restore_context = am33xx_pwrdm_restore_context,
|
|
};
|
|
|
|
static struct prm_ll_data am33xx_prm_ll_data = {
|
|
.assert_hardreset = am33xx_prm_assert_hardreset,
|
|
.deassert_hardreset = am33xx_prm_deassert_hardreset,
|
|
.is_hardreset_asserted = am33xx_prm_is_hardreset_asserted,
|
|
.reset_system = am33xx_prm_global_warm_sw_reset,
|
|
};
|
|
|
|
int __init am33xx_prm_init(const struct omap_prcm_init_data *data)
|
|
{
|
|
return prm_register(&am33xx_prm_ll_data);
|
|
}
|
|
|
|
static void __exit am33xx_prm_exit(void)
|
|
{
|
|
prm_unregister(&am33xx_prm_ll_data);
|
|
}
|
|
__exitcall(am33xx_prm_exit);
|