blob: 9e125f10dfec64c3d438987568d55365a2f7ecee [file] [log] [blame]
/*
* intel_soc_mrfld.c - This driver provides utility api's for merrifield
* platform
* Copyright (c) 2012, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include "intel_soc_pmu.h"
u32 __iomem *residency[SYS_STATE_MAX];
u32 __iomem *s0ix_counter[SYS_STATE_MAX];
/* list of north complex devices */
char *mrfl_nc_devices[] = {
"GFXSLC",
"GSDKCK",
"GRSCD",
"VED",
"VEC",
"DPA",
"DPB",
"DPC",
"VSP",
"ISP",
"MIO",
"HDMIO",
"GFXSLCLDO"
};
int mrfl_no_of_nc_devices =
sizeof(mrfl_nc_devices)/sizeof(mrfl_nc_devices[0]);
static int mrfld_pmu_init(void)
{
mid_pmu_cxt->s3_hint = MRFLD_S3_HINT;
/* Put all unused LSS in D0i3 */
mid_pmu_cxt->os_sss[0] = (SSMSK(D0I3_MASK, PMU_RESERVED_LSS_03) |
SSMSK(D0I3_MASK, PMU_HSI_LSS_05) |
//SSMSK(D0I3_MASK, PMU_SECURITY_LSS_06) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_07) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_11) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_12) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_13) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_14) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_15));
/* Put LSS8 as unused on Tangier */
mid_pmu_cxt->os_sss[0] |= \
SSMSK(D0I3_MASK, PMU_USB_MPH_LSS_08);
mid_pmu_cxt->os_sss[1] = (SSMSK(D0I3_MASK, PMU_RESERVED_LSS_16-16)|
SSMSK(D0I3_MASK, PMU_SSP3_LSS_17-16)|
SSMSK(D0I3_MASK, PMU_SSP6_LSS_19-16)|
SSMSK(D0I3_MASK, PMU_USB_OTG_LSS_28-16)|
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_29-16)|
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_30-16));
/* Excpet for LSS 35 keep all in D0i3 */
mid_pmu_cxt->os_sss[2] = 0xFFFFFFFF;
mid_pmu_cxt->os_sss[3] = 0xFFFFFFFF;
mid_pmu_cxt->os_sss[2] &= ~SSMSK(D0I3_MASK, PMU_SSP4_LSS_35-32);
/* Map S0ix residency counters */
residency[SYS_STATE_S0I1] = ioremap_nocache(S0I1_RES_ADDR, sizeof(u64));
if (residency[SYS_STATE_S0I1] == NULL)
goto err1;
residency[SYS_STATE_LPMP3] = ioremap_nocache(LPMP3_RES_ADDR,
sizeof(u64));
if (residency[SYS_STATE_LPMP3] == NULL)
goto err2;
residency[SYS_STATE_S0I2] = ioremap_nocache(S0I2_RES_ADDR, sizeof(u64));
if (residency[SYS_STATE_S0I2] == NULL)
goto err3;
residency[SYS_STATE_S0I3] = ioremap_nocache(S0I3_RES_ADDR, sizeof(u64));
if (residency[SYS_STATE_S0I3] == NULL)
goto err4;
/* Map S0ix iteration counters */
s0ix_counter[SYS_STATE_S0I1] = ioremap_nocache(S0I1_COUNT_ADDR,
sizeof(u32));
if (s0ix_counter[SYS_STATE_S0I1] == NULL)
goto err5;
s0ix_counter[SYS_STATE_LPMP3] = ioremap_nocache(LPMP3_COUNT_ADDR,
sizeof(u32));
if (s0ix_counter[SYS_STATE_LPMP3] == NULL)
goto err6;
s0ix_counter[SYS_STATE_S0I2] = ioremap_nocache(S0I2_COUNT_ADDR,
sizeof(u32));
if (s0ix_counter[SYS_STATE_S0I2] == NULL)
goto err7;
s0ix_counter[SYS_STATE_S0I3] = ioremap_nocache(S0I3_COUNT_ADDR,
sizeof(u32));
if (s0ix_counter[SYS_STATE_S0I3] == NULL)
goto err8;
/* Keep PSH LSS's 00, 33, 34 in D0i0 if PM is disabled */
if (!enable_s0ix && !enable_s3) {
mid_pmu_cxt->os_sss[2] &=
~SSMSK(D0I3_MASK, PMU_I2C8_LSS_33-32);
mid_pmu_cxt->os_sss[2] &=
~SSMSK(D0I3_MASK, PMU_I2C9_LSS_34-32);
} else {
mid_pmu_cxt->os_sss[0] |= SSMSK(D0I3_MASK, PMU_PSH_LSS_00);
}
/* Disable the Interrupt Enable bit in PM ICS register */
pmu_clear_interrupt_enable();
return PMU_SUCCESS;
err8:
iounmap(s0ix_counter[SYS_STATE_S0I3]);
s0ix_counter[SYS_STATE_S0I3] = NULL;
err7:
iounmap(s0ix_counter[SYS_STATE_S0I2]);
s0ix_counter[SYS_STATE_S0I2] = NULL;
err6:
iounmap(s0ix_counter[SYS_STATE_LPMP3]);
s0ix_counter[SYS_STATE_LPMP3] = NULL;
err5:
iounmap(s0ix_counter[SYS_STATE_S0I1]);
s0ix_counter[SYS_STATE_S0I1] = NULL;
err4:
iounmap(residency[SYS_STATE_S0I3]);
residency[SYS_STATE_S0I3] = NULL;
err3:
iounmap(residency[SYS_STATE_S0I2]);
residency[SYS_STATE_S0I2] = NULL;
err2:
iounmap(residency[SYS_STATE_LPMP3]);
residency[SYS_STATE_LPMP3] = NULL;
err1:
iounmap(residency[SYS_STATE_S0I1]);
residency[SYS_STATE_S0I1] = NULL;
pr_err("Cannot map memory to read S0ix residency and count\n");
return PMU_FAILED;
}
/* This function checks north complex (NC) and
* south complex (SC) device status in MRFLD.
* returns TRUE if all NC and SC devices are in d0i3
* else FALSE.
*/
static bool mrfld_nc_sc_status_check(void)
{
int i;
u32 val, nc_pwr_sts;
struct pmu_ss_states cur_pmsss;
bool nc_status, sc_status;
/* assuming nc and sc are good */
nc_status = true;
sc_status = true;
/* Check south complex device status */
pmu_read_sss(&cur_pmsss);
if (!(((cur_pmsss.pmu2_states[0] & S0IX_TARGET_SSS0_MASK) ==
S0IX_TARGET_SSS0) &&
((cur_pmsss.pmu2_states[1] & S0IX_TARGET_SSS1_MASK) ==
S0IX_TARGET_SSS1) &&
((cur_pmsss.pmu2_states[2] & S0IX_TARGET_SSS2_MASK) ==
S0IX_TARGET_SSS2) &&
((cur_pmsss.pmu2_states[3] & S0IX_TARGET_SSS3_MASK) ==
(S0IX_TARGET_SSS3)))) {
sc_status = false;
pr_warn("SC device/devices not in d0i3!!\n");
for (i = 0; i < 4; i++)
pr_warn("pmu2_states[%d] = %08lX\n", i,
cur_pmsss.pmu2_states[i]);
}
if (sc_status) {
/* Check north complex status */
nc_pwr_sts =
intel_mid_msgbus_read32(PUNIT_PORT, NC_PM_SSS);
/* loop through the status to see if any of nc power island
* is not in D0i3 state
*/
for (i = 0; i < mrfl_no_of_nc_devices; i++) {
val = nc_pwr_sts & 3;
if (val != 3) {
nc_status = false;
pr_warn("NC device (%s) is not in d0i3!!\n",
mrfl_nc_devices[i]);
pr_warn("nc_pm_sss = %08X\n", nc_pwr_sts);
break;
}
nc_pwr_sts >>= BITS_PER_LSS;
}
}
return nc_status & sc_status;
}
/* FIXME: Need to start the counter only if debug is
* needed. This will save SCU cycles if debug is
* disabled
*/
static int __init start_scu_s0ix_res_counters(void)
{
int ret;
ret = intel_scu_ipc_simple_command(START_RES_COUNTER, 0);
if (ret) {
pr_err("IPC command to start res counter failed\n");
BUG();
return ret;
}
return 0;
}
late_initcall(start_scu_s0ix_res_counters);
void platform_update_all_lss_states(struct pmu_ss_states *pmu_config,
int *PCIALLDEV_CFG)
{
/* Overwrite the pmu_config values that we get */
pmu_config->pmu2_states[0] =
(SSMSK(D0I3_MASK, PMU_RESERVED_LSS_03) |
SSMSK(D0I3_MASK, PMU_HSI_LSS_05) |
//SSMSK(D0I3_MASK, PMU_SECURITY_LSS_06) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_07) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_11) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_12) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_13) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_14) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_15));
/* Put LSS8 as unused on Tangier */
pmu_config->pmu2_states[0] |= \
SSMSK(D0I3_MASK, PMU_USB_MPH_LSS_08);
pmu_config->pmu2_states[1] =
(SSMSK(D0I3_MASK, PMU_RESERVED_LSS_16-16)|
SSMSK(D0I3_MASK, PMU_SSP3_LSS_17-16)|
SSMSK(D0I3_MASK, PMU_SSP6_LSS_19-16)|
SSMSK(D0I3_MASK, PMU_USB_OTG_LSS_28-16) |
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_29-16)|
SSMSK(D0I3_MASK, PMU_RESERVED_LSS_30-16));
pmu_config->pmu2_states[0] &= ~IGNORE_SSS0;
pmu_config->pmu2_states[1] &= ~IGNORE_SSS1;
pmu_config->pmu2_states[2] = ~IGNORE_SSS2;
pmu_config->pmu2_states[3] = ~IGNORE_SSS3;
/* Excpet for LSS 35 keep all in D0i3 */
pmu_config->pmu2_states[2] &= ~SSMSK(D0I3_MASK, PMU_SSP4_LSS_35-32);
/* Keep PSH LSS's 00, 33, 34 in D0i0 if PM is disabled */
if (!enable_s0ix && !enable_s3) {
pmu_config->pmu2_states[2] &=
~SSMSK(D0I3_MASK, PMU_I2C8_LSS_33-32);
pmu_config->pmu2_states[2] &=
~SSMSK(D0I3_MASK, PMU_I2C9_LSS_34-32);
} else {
pmu_config->pmu2_states[0] |= SSMSK(D0I3_MASK, PMU_PSH_LSS_00);
}
}
/*
* In MDFLD and CLV this callback is used to issue
* PM_CMD which is not required in MRFLD
*/
static bool mrfld_pmu_enter(int s0ix_state)
{
mid_pmu_cxt->s0ix_entered = s0ix_state;
if (s0ix_state == MID_S3_STATE) {
mid_pmu_cxt->pmu_current_state = SYS_STATE_S3;
pmu_set_interrupt_enable();
}
return true;
}
/**
* platform_set_pmu_ops - Set the global pmu method table.
* @ops: Pointer to ops structure.
*/
void platform_set_pmu_ops(void)
{
pmu_ops = &mrfld_pmu_ops;
}
/*
* As of now since there is no sequential mapping between
* LSS abd WKS bits the following two calls are dummy
*/
bool mid_pmu_is_wake_source(u32 lss_number)
{
return false;
}
/* return the last wake source id, and make statistics about wake sources */
int pmu_get_wake_source(void)
{
return INVALID_WAKE_SRC;
}
int set_extended_cstate_mode(const char *val, struct kernel_param *kp)
{
return 0;
}
int get_extended_cstate_mode(char *buffer, struct kernel_param *kp)
{
const char *default_string = "not supported";
strcpy(buffer, default_string);
return strlen(default_string);
}
static int wait_for_nc_pmcmd_complete(int verify_mask,
int status_mask, int state_type , int reg)
{
int pwr_sts;
int count = 0;
while (true) {
pwr_sts = intel_mid_msgbus_read32(PUNIT_PORT, reg);
pwr_sts = pwr_sts >> SSS_SHIFT;
if (state_type == OSPM_ISLAND_DOWN ||
state_type == OSPM_ISLAND_SR) {
if ((pwr_sts & status_mask) ==
(verify_mask & status_mask))
break;
else
udelay(10);
} else if (state_type == OSPM_ISLAND_UP) {
if ((~pwr_sts & status_mask) ==
(~verify_mask & status_mask))
break;
else
udelay(10);
}
count++;
if (WARN_ONCE(count > 500000, "Timed out waiting for P-Unit"))
return -EBUSY;
}
return 0;
}
static int mrfld_nc_set_power_state(int islands, int state_type,
int reg, int *change)
{
u32 pwr_sts = 0;
u32 pwr_mask = 0;
int i, lss, mask;
int ret = 0;
int status_mask = 0;
*change = 0;
pwr_sts = intel_mid_msgbus_read32(PUNIT_PORT, reg);
pwr_mask = pwr_sts;
for (i = 0; i < OSPM_MAX_POWER_ISLANDS; i++) {
lss = islands & (0x1 << i);
if (lss) {
mask = D0I3_MASK << (BITS_PER_LSS * i);
status_mask = status_mask | mask;
if (state_type == OSPM_ISLAND_DOWN)
pwr_mask |= mask;
else if (state_type == OSPM_ISLAND_UP)
pwr_mask &= ~mask;
/* Soft reset case */
else if (state_type == OSPM_ISLAND_SR) {
pwr_mask &= ~mask;
mask = SR_MASK << (BITS_PER_LSS * i);
pwr_mask |= mask;
}
}
}
if (pwr_mask != pwr_sts) {
intel_mid_msgbus_write32(PUNIT_PORT, reg, pwr_mask);
ret = wait_for_nc_pmcmd_complete(pwr_mask,
status_mask, state_type, reg);
if (!ret)
*change = 1;
if (nc_report_power_state)
nc_report_power_state(pwr_mask, reg);
}
return ret;
}
void s0ix_complete(void)
{
if (mid_pmu_cxt->s0ix_entered) {
log_wakeup_irq();
if (mid_pmu_cxt->s0ix_entered == SYS_STATE_S3)
pmu_clear_interrupt_enable();
mid_pmu_cxt->pmu_current_state =
mid_pmu_cxt->s0ix_entered = 0;
}
}
bool could_do_s0ix(void)
{
bool ret = false;
if (unlikely(!pmu_initialized))
goto ret;
/* dont do s0ix if suspend in progress */
if (unlikely(mid_pmu_cxt->suspend_started))
goto ret;
/* dont do s0ix if shutdown in progress */
if (unlikely(mid_pmu_cxt->shutdown_started))
goto ret;
if (nc_device_state())
goto ret;
ret = true;
ret:
return ret;
}
EXPORT_SYMBOL(could_do_s0ix);
struct platform_pmu_ops mrfld_pmu_ops = {
.init = mrfld_pmu_init,
.enter = mrfld_pmu_enter,
.set_s0ix_complete = s0ix_complete,
.nc_set_power_state = mrfld_nc_set_power_state,
.check_nc_sc_status = mrfld_nc_sc_status_check,
};