blob: ea0b56c6a89ab10eeaa6e53cf6e55736f4cc4651 [file] [log] [blame]
/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/io.h>
#include <mach/msm_iomap.h>
#include "spm.h"
enum {
MSM_SPM_DEBUG_SHADOW = 1U << 0,
MSM_SPM_DEBUG_VCTL = 1U << 1,
};
static int msm_spm_debug_mask;
module_param_named(
debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
);
#define MSM_SPM_PMIC_STATE_IDLE 0
static uint32_t msm_spm_reg_offsets[MSM_SPM_REG_NR] = {
[MSM_SPM_REG_SAW_AVS_CTL] = 0x04,
[MSM_SPM_REG_SAW_VCTL] = 0x08,
[MSM_SPM_REG_SAW_STS] = 0x0C,
[MSM_SPM_REG_SAW_CFG] = 0x10,
[MSM_SPM_REG_SAW_SPM_CTL] = 0x14,
[MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0x18,
[MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0x1C,
[MSM_SPM_REG_SAW_SPM_PMIC_CTL] = 0x20,
[MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x24,
[MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x28,
[MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x2C,
[MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x30,
[MSM_SPM_REG_SAW_SLP_RST_EN] = 0x34,
[MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x38,
};
struct msm_spm_device {
void __iomem *reg_base_addr;
uint32_t reg_shadow[MSM_SPM_REG_NR];
uint8_t awake_vlevel;
uint8_t retention_vlevel;
uint8_t collapse_vlevel;
uint8_t retention_mid_vlevel;
uint8_t collapse_mid_vlevel;
uint32_t vctl_timeout_us;
unsigned int low_power_mode;
bool notify_rpm;
bool dirty;
};
static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_spm_devices);
/******************************************************************************
* Internal helper functions
*****************************************************************************/
static inline void msm_spm_set_vctl(
struct msm_spm_device *dev, uint32_t vlevel)
{
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0xFF;
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= vlevel;
}
static inline void msm_spm_set_spm_ctl(struct msm_spm_device *dev,
uint32_t rpm_bypass, uint32_t mode_encoding)
{
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] &= ~0x0F;
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= rpm_bypass << 3;
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= mode_encoding;
}
static inline void msm_spm_set_pmic_ctl(struct msm_spm_device *dev,
uint32_t awake_vlevel, uint32_t mid_vlevel, uint32_t sleep_vlevel)
{
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_PMIC_CTL] =
(mid_vlevel << 16) | (awake_vlevel << 8) | (sleep_vlevel);
}
static inline void msm_spm_set_slp_rst_en(
struct msm_spm_device *dev, uint32_t slp_rst_en)
{
dev->reg_shadow[MSM_SPM_REG_SAW_SLP_RST_EN] = slp_rst_en;
}
static inline void msm_spm_flush_shadow(
struct msm_spm_device *dev, unsigned int reg_index)
{
__raw_writel(dev->reg_shadow[reg_index],
dev->reg_base_addr + msm_spm_reg_offsets[reg_index]);
}
static inline void msm_spm_load_shadow(
struct msm_spm_device *dev, unsigned int reg_index)
{
dev->reg_shadow[reg_index] = __raw_readl(dev->reg_base_addr +
msm_spm_reg_offsets[reg_index]);
}
static inline uint32_t msm_spm_get_sts_pmic_state(struct msm_spm_device *dev)
{
return (dev->reg_shadow[MSM_SPM_REG_SAW_STS] >> 20) & 0x03;
}
static inline uint32_t msm_spm_get_sts_curr_pmic_data(
struct msm_spm_device *dev)
{
return (dev->reg_shadow[MSM_SPM_REG_SAW_STS] >> 10) & 0xFF;
}
/******************************************************************************
* Public functions
*****************************************************************************/
/**
* msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
* @mode: SPM LPM mode to enter
* @notify_rpm: Notify RPM in this mode
*/
int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
{
struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices);
uint32_t rpm_bypass = notify_rpm ? 0x00 : 0x01;
if (mode == dev->low_power_mode && notify_rpm == dev->notify_rpm
&& !dev->dirty)
return 0;
switch (mode) {
case MSM_SPM_MODE_CLOCK_GATING:
msm_spm_set_spm_ctl(dev, rpm_bypass, 0x00);
msm_spm_set_slp_rst_en(dev, 0x00);
break;
case MSM_SPM_MODE_POWER_RETENTION:
msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02);
msm_spm_set_pmic_ctl(dev, dev->awake_vlevel,
dev->retention_mid_vlevel, dev->retention_vlevel);
msm_spm_set_slp_rst_en(dev, 0x00);
break;
case MSM_SPM_MODE_POWER_COLLAPSE:
msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02);
msm_spm_set_pmic_ctl(dev, dev->awake_vlevel,
dev->collapse_mid_vlevel, dev->collapse_vlevel);
msm_spm_set_slp_rst_en(dev, 0x01);
break;
default:
BUG();
}
msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_PMIC_CTL);
msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SLP_RST_EN);
/* Ensure that the registers are written before returning */
mb();
dev->low_power_mode = mode;
dev->notify_rpm = notify_rpm;
dev->dirty = false;
if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
int i;
for (i = 0; i < MSM_SPM_REG_NR; i++)
pr_info("%s: reg %02x = 0x%08x\n", __func__,
msm_spm_reg_offsets[i], dev->reg_shadow[i]);
}
return 0;
}
/**
* msm_spm_set_vdd(): Set core voltage
* @cpu: core id
* @vlevel: Encoded PMIC data.
*/
int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
{
struct msm_spm_device *dev;
uint32_t timeout_us;
dev = &per_cpu(msm_spm_devices, cpu);
if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
pr_info("%s: requesting cpu %u vlevel 0x%x\n",
__func__, cpu, vlevel);
msm_spm_set_vctl(dev, vlevel);
msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL);
/* Wait for PMIC state to return to idle or until timeout */
timeout_us = dev->vctl_timeout_us;
msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS);
while (msm_spm_get_sts_pmic_state(dev) != MSM_SPM_PMIC_STATE_IDLE) {
if (!timeout_us)
goto set_vdd_bail;
if (timeout_us > 10) {
udelay(10);
timeout_us -= 10;
} else {
udelay(timeout_us);
timeout_us = 0;
}
msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS);
}
if (msm_spm_get_sts_curr_pmic_data(dev) != vlevel)
goto set_vdd_bail;
dev->awake_vlevel = vlevel;
dev->dirty = true;
if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
pr_info("%s: cpu %u done, remaining timeout %uus\n",
__func__, cpu, timeout_us);
return 0;
set_vdd_bail:
pr_err("%s: cpu %u failed, remaining timeout %uus, vlevel 0x%x\n",
__func__, cpu, timeout_us, msm_spm_get_sts_curr_pmic_data(dev));
return -EIO;
}
/**
* msm_spm_get_vdd(): Get core voltage
* @cpu: core id
* @return: Returns encoded PMIC data.
*/
unsigned int msm_spm_get_vdd(unsigned int cpu)
{
struct msm_spm_device *dev = &per_cpu(msm_spm_devices, cpu);
return dev->reg_shadow[MSM_SPM_REG_SAW_VCTL];
}
void msm_spm_reinit(void)
{
struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices);
int i;
for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
msm_spm_flush_shadow(dev, i);
/* Ensure that the registers are written before returning */
mb();
}
/**
* msm_spm_init(): Board initalization function
* @data: platform specific SPM register configuration data
* @nr_devs: Number of SPM devices being initialized
*/
int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
{
unsigned int cpu;
BUG_ON(nr_devs < num_possible_cpus());
for_each_possible_cpu(cpu) {
struct msm_spm_device *dev = &per_cpu(msm_spm_devices, cpu);
int i;
dev->reg_base_addr = data[cpu].reg_base_addr;
memcpy(dev->reg_shadow, data[cpu].reg_init_values,
sizeof(data[cpu].reg_init_values));
dev->awake_vlevel = data[cpu].awake_vlevel;
dev->retention_vlevel = data[cpu].retention_vlevel;
dev->collapse_vlevel = data[cpu].collapse_vlevel;
dev->retention_mid_vlevel = data[cpu].retention_mid_vlevel;
dev->collapse_mid_vlevel = data[cpu].collapse_mid_vlevel;
dev->vctl_timeout_us = data[cpu].vctl_timeout_us;
for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
msm_spm_flush_shadow(dev, i);
/* Ensure that the registers are written before returning */
mb();
dev->low_power_mode = MSM_SPM_MODE_CLOCK_GATING;
dev->notify_rpm = false;
dev->dirty = true;
}
return 0;
}