blob: fcf5bf56f08fcfade32f7fd026fc674b4adc32b2 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* GXP local power management interface.
*
* Copyright (C) 2021 Google LLC
*/
#include <linux/acpm_dvfs.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include "gxp-bpm.h"
#include "gxp-config.h"
#include "gxp-doorbell.h"
#include "gxp-internal.h"
#include "gxp-lpm.h"
#if IS_GXP_TEST
#define gxp_lpm_wait_until(ret, ...) ((ret) = true)
#else
#define gxp_lpm_wait_until(ret, lpm_state, condition) \
do { \
int i = 100000; \
while (i) { \
lpm_state = lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET) & \
PSM_CURR_STATE_MASK; \
if (condition) \
break; \
udelay(2 * GXP_TIME_DELAY_FACTOR); \
i--; \
} \
ret = (i != 0); \
} while (0)
#endif
void gxp_lpm_enable_state(struct gxp_dev *gxp, enum gxp_lpm_psm psm, uint state)
{
/* PS0 should always be enabled */
if (state == LPM_ACTIVE_STATE || state > LPM_PG_STATE)
return;
/* Disable all low power states */
lpm_write_32_psm(gxp, psm, PSM_REG_ENABLE_STATE1_OFFSET, 0x0);
lpm_write_32_psm(gxp, psm, PSM_REG_ENABLE_STATE2_OFFSET, 0x0);
lpm_write_32_psm(gxp, psm, PSM_REG_ENABLE_STATE3_OFFSET, 0x0);
/* Enable the requested low power state */
lpm_write_32_psm(gxp, psm, state, 0x1);
}
bool gxp_lpm_is_initialized(struct gxp_dev *gxp, enum gxp_lpm_psm psm)
{
u32 status = lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET);
/*
* state_valid bit goes active and stays high forever the first time you
* write the start register
*/
if (status & PSM_STATE_VALID_MASK)
return true;
return false;
}
bool gxp_lpm_is_powered(struct gxp_dev *gxp, enum gxp_lpm_psm psm)
{
u32 status = lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET);
u32 state;
if (!(status & PSM_STATE_VALID_MASK))
return false;
state = status & PSM_CURR_STATE_MASK;
return state == LPM_ACTIVE_STATE || state == LPM_CG_STATE;
}
uint gxp_lpm_get_state(struct gxp_dev *gxp, enum gxp_lpm_psm psm)
{
u32 status = lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET);
return status & PSM_CURR_STATE_MASK;
}
static int set_state_internal(struct gxp_dev *gxp, enum gxp_lpm_psm psm, uint target_state)
{
u32 val;
int i = 10000;
/* Set SW sequencing mode and PS target */
val = LPM_SW_PSM_MODE;
val |= target_state << LPM_CFG_SW_PS_TARGET_OFFSET;
lpm_write_32_psm(gxp, psm, PSM_REG_CFG_OFFSET, val);
/* Start the SW sequence */
lpm_write_32_psm(gxp, psm, PSM_REG_START_OFFSET, 0x1);
/* Wait for LPM init done (0x60041688) */
while (i && !(lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET)
& PSM_INIT_DONE_MASK)) {
udelay(1 * GXP_TIME_DELAY_FACTOR);
i--;
}
if (!i) {
dev_err(gxp->dev, "Failed to switch PSM%u to PS%u\n", psm, target_state);
return -EIO;
}
return 0;
}
int gxp_lpm_set_state(struct gxp_dev *gxp, enum gxp_lpm_psm psm, uint target_state,
bool verbose)
{
uint curr_state = gxp_lpm_get_state(gxp, psm);
if (curr_state == target_state)
return 0;
if (verbose)
dev_warn(gxp->dev,
"Forcing a transition to PS%u on core%u, status: %x\n",
target_state, psm,
lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET));
gxp_lpm_enable_state(gxp, psm, target_state);
if ((curr_state != LPM_ACTIVE_STATE)
&& (target_state != LPM_ACTIVE_STATE)) {
/* Switch to PS0 before switching to a low power state. */
set_state_internal(gxp, psm, LPM_ACTIVE_STATE);
}
set_state_internal(gxp, psm, target_state);
if (verbose)
dev_warn(
gxp->dev,
"Finished forced transition on core %u. target: PS%u, actual: PS%u, status: %x\n",
psm, target_state, gxp_lpm_get_state(gxp, psm),
lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET));
/* Set HW sequencing mode */
lpm_write_32_psm(gxp, psm, PSM_REG_CFG_OFFSET, LPM_HW_MODE);
return 0;
}
static int psm_enable(struct gxp_dev *gxp, enum gxp_lpm_psm psm)
{
int i = 10000;
/* Return early if LPM is already initialized */
if (gxp_lpm_is_initialized(gxp, psm)) {
if (psm != LPM_PSM_TOP) {
/* Ensure core is in PS3 */
return gxp_lpm_set_state(gxp, psm, LPM_PG_STATE,
/*verbose=*/true);
}
return 0;
}
/* Write PSM start bit */
lpm_write_32_psm(gxp, psm, PSM_REG_START_OFFSET, PSM_START);
/* Wait for LPM init done (0x60041688) */
while (i && !(lpm_read_32_psm(gxp, psm, PSM_REG_STATUS_OFFSET)
& PSM_INIT_DONE_MASK)) {
udelay(1 * GXP_TIME_DELAY_FACTOR);
i--;
}
if (!i)
return 1;
/* Set PSM to HW mode (0x60041680) */
lpm_write_32_psm(gxp, psm, PSM_REG_CFG_OFFSET, PSM_HW_MODE);
return 0;
}
void gxp_lpm_init(struct gxp_dev *gxp)
{
/* Enable Top PSM */
if (psm_enable(gxp, LPM_PSM_TOP))
dev_err(gxp->dev, "Timed out when enabling Top PSM!\n");
}
void gxp_lpm_destroy(struct gxp_dev *gxp)
{
/* (b/171063370) Put Top PSM in ACTIVE state before block shutdown */
dev_dbg(gxp->dev, "Kicking Top PSM out of ACG\n");
/* Disable all low-power states for TOP */
lpm_write_32_psm(gxp, LPM_PSM_TOP, PSM_REG_ENABLE_STATE1_OFFSET, 0x0);
lpm_write_32_psm(gxp, LPM_PSM_TOP, PSM_REG_ENABLE_STATE2_OFFSET, 0x0);
}
int gxp_lpm_up(struct gxp_dev *gxp, uint core)
{
/* Clear wakeup doorbell */
gxp_doorbell_clear(gxp, CORE_WAKEUP_DOORBELL(core));
/* Enable core PSM */
if (psm_enable(gxp, CORE_TO_PSM(core))) {
dev_err(gxp->dev, "Timed out when enabling Core%u PSM!\n",
core);
return -ETIMEDOUT;
}
/* Enable PS1 (Clk Gated). Only required for core PSMs. */
if (core < GXP_NUM_CORES)
gxp_lpm_enable_state(gxp, CORE_TO_PSM(core), LPM_CG_STATE);
gxp_bpm_start(gxp, core);
return 0;
}
void gxp_lpm_down(struct gxp_dev *gxp, uint core)
{
if (gxp_lpm_get_state(gxp, CORE_TO_PSM(core)) == LPM_PG_STATE)
return;
/* Enable PS3 (Pwr Gated) */
gxp_lpm_enable_state(gxp, CORE_TO_PSM(core), LPM_PG_STATE);
/* Set wakeup doorbell to trigger an automatic transition to PS3 */
gxp_doorbell_enable_for_core(gxp, CORE_WAKEUP_DOORBELL(core), core);
gxp_doorbell_set(gxp, CORE_WAKEUP_DOORBELL(core));
msleep(25 * GXP_TIME_DELAY_FACTOR);
/*
* Clear the core's interrupt mask and the wakeup doorbell to ensure
* the core will not wake unexpectedly.
*/
gxp_write_32(gxp, GXP_CORE_REG_COMMON_INT_MASK_0(core), 0);
gxp_doorbell_clear(gxp, CORE_WAKEUP_DOORBELL(core));
/* Ensure core is in PS3 */
gxp_lpm_set_state(gxp, CORE_TO_PSM(core), LPM_PG_STATE, /*verbose=*/true);
}
bool gxp_lpm_wait_state_ne(struct gxp_dev *gxp, enum gxp_lpm_psm psm, uint state)
{
__maybe_unused uint lpm_state;
bool ret;
gxp_lpm_wait_until(ret, lpm_state, lpm_state != state);
return ret;
}
bool gxp_lpm_wait_state_eq(struct gxp_dev *gxp, enum gxp_lpm_psm psm, uint state)
{
__maybe_unused uint lpm_state;
bool ret;
gxp_lpm_wait_until(ret, lpm_state, lpm_state == state);
return ret;
}