drivers: soc: qcom: Enable SPM driver

Enables the support SPM and AVS wrapper hardware.

Change-Id: I2525d5fadf5be318f55ce722c26e598640af926f
Signed-off-by: Raghavendra Kakarla <rkakarla@codeaurora.org>
Signed-off-by: Tushar Nimkar <tnimkar@codeaurora.org>
This commit is contained in:
Raghavendra Kakarla 2017-11-16 12:28:54 +05:30 committed by Gerrit - the friendly Code Review server
parent baae1562c6
commit e48c59bf5a
6 changed files with 2085 additions and 0 deletions

View File

@ -509,6 +509,22 @@ config QCOM_SMD_RPM
Say M here if you want to include support for the Qualcomm RPM as a
module. This will build a module called "qcom-smd-rpm".
config MSM_SPM
bool "Driver support for SPM and AVS wrapper hardware"
help
Enables the support for SPM and AVS wrapper hardware on MSMs. SPM
hardware is used to manage the processor power during sleep. The
driver allows configuring SPM to allow different low power modes for
both core and L2.
config MSM_L2_SPM
bool "SPM support for L2 cache"
help
Enable SPM driver support for L2 cache. Some MSM chipsets allow
control of L2 cache low power mode with a Subsystem Power manager.
Enabling this driver allows configuring L2 SPM for low power modes
on supported chipsets.
config QCOM_MEMORY_DUMP_V2
tristate "QCOM Memory Dump V2 Support"
help

View File

@ -27,6 +27,7 @@ obj-$(CONFIG_QCOM_SOC_SLEEP_STATS) += soc_sleep_stats.o
obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o
obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
obj-$(CONFIG_QCOM_SMEM) += smem.o
obj-$(CONFIG_MSM_SPM) += msm-spm.o spm_devices.o
obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SUBSYSTEM_SLEEP_STATS) += subsystem_sleep_stats.o

762
drivers/soc/qcom/msm-spm.c Normal file
View File

@ -0,0 +1,762 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2011-2017, 2020-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/slab.h>
#include "spm_driver.h"
#define MSM_SPM_PMIC_STATE_IDLE 0
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, 0664
);
struct saw2_data {
const char *ver_name;
uint32_t major;
uint32_t minor;
uint32_t *spm_reg_offset_ptr;
};
static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
[MSM_SPM_REG_SAW_SECURE] = 0x00,
[MSM_SPM_REG_SAW_ID] = 0x04,
[MSM_SPM_REG_SAW_CFG] = 0x08,
[MSM_SPM_REG_SAW_SPM_STS] = 0x0C,
[MSM_SPM_REG_SAW_AVS_STS] = 0x10,
[MSM_SPM_REG_SAW_PMIC_STS] = 0x14,
[MSM_SPM_REG_SAW_RST] = 0x18,
[MSM_SPM_REG_SAW_VCTL] = 0x1C,
[MSM_SPM_REG_SAW_AVS_CTL] = 0x20,
[MSM_SPM_REG_SAW_AVS_LIMIT] = 0x24,
[MSM_SPM_REG_SAW_AVS_DLY] = 0x28,
[MSM_SPM_REG_SAW_AVS_HYSTERESIS] = 0x2C,
[MSM_SPM_REG_SAW_SPM_CTL] = 0x30,
[MSM_SPM_REG_SAW_SPM_DLY] = 0x34,
[MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40,
[MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44,
[MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48,
[MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C,
[MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50,
[MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54,
[MSM_SPM_REG_SAW_PMIC_DATA_6] = 0x58,
[MSM_SPM_REG_SAW_PMIC_DATA_7] = 0x5C,
[MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x80,
[MSM_SPM_REG_SAW_VERSION] = 0xFD0,
};
static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
[MSM_SPM_REG_SAW_SECURE] = 0x00,
[MSM_SPM_REG_SAW_ID] = 0x04,
[MSM_SPM_REG_SAW_CFG] = 0x08,
[MSM_SPM_REG_SAW_SPM_STS] = 0x0C,
[MSM_SPM_REG_SAW_AVS_STS] = 0x10,
[MSM_SPM_REG_SAW_PMIC_STS] = 0x14,
[MSM_SPM_REG_SAW_RST] = 0x18,
[MSM_SPM_REG_SAW_VCTL] = 0x1C,
[MSM_SPM_REG_SAW_AVS_CTL] = 0x20,
[MSM_SPM_REG_SAW_AVS_LIMIT] = 0x24,
[MSM_SPM_REG_SAW_AVS_DLY] = 0x28,
[MSM_SPM_REG_SAW_AVS_HYSTERESIS] = 0x2C,
[MSM_SPM_REG_SAW_SPM_CTL] = 0x30,
[MSM_SPM_REG_SAW_SPM_DLY] = 0x34,
[MSM_SPM_REG_SAW_STS2] = 0x38,
[MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40,
[MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44,
[MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48,
[MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C,
[MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50,
[MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54,
[MSM_SPM_REG_SAW_PMIC_DATA_6] = 0x58,
[MSM_SPM_REG_SAW_PMIC_DATA_7] = 0x5C,
[MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x400,
[MSM_SPM_REG_SAW_VERSION] = 0xFD0,
};
static uint32_t msm_spm_reg_offsets_saw2_v4_1[MSM_SPM_REG_NR] = {
[MSM_SPM_REG_SAW_SECURE] = 0xC00,
[MSM_SPM_REG_SAW_ID] = 0xC04,
[MSM_SPM_REG_SAW_STS2] = 0xC10,
[MSM_SPM_REG_SAW_SPM_STS] = 0xC0C,
[MSM_SPM_REG_SAW_AVS_STS] = 0xC14,
[MSM_SPM_REG_SAW_PMIC_STS] = 0xC18,
[MSM_SPM_REG_SAW_RST] = 0xC1C,
[MSM_SPM_REG_SAW_VCTL] = 0x900,
[MSM_SPM_REG_SAW_AVS_CTL] = 0x904,
[MSM_SPM_REG_SAW_AVS_LIMIT] = 0x908,
[MSM_SPM_REG_SAW_AVS_DLY] = 0x90C,
[MSM_SPM_REG_SAW_SPM_CTL] = 0x0,
[MSM_SPM_REG_SAW_SPM_DLY] = 0x4,
[MSM_SPM_REG_SAW_CFG] = 0x0C,
[MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40,
[MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44,
[MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48,
[MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C,
[MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50,
[MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54,
[MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x400,
[MSM_SPM_REG_SAW_VERSION] = 0xFD0,
};
static struct saw2_data saw2_info[] = {
[0] = {
"SAW_v2.1",
0x2,
0x1,
msm_spm_reg_offsets_saw2_v2_1,
},
[1] = {
"SAW_v2.3",
0x3,
0x0,
msm_spm_reg_offsets_saw2_v3_0,
},
[2] = {
"SAW_v3.0",
0x1,
0x0,
msm_spm_reg_offsets_saw2_v3_0,
},
[3] = {
"SAW_v4.0",
0x4,
0x1,
msm_spm_reg_offsets_saw2_v4_1,
},
};
static uint32_t num_pmic_data;
static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
unsigned int reg_index)
{
if (!dev)
return;
__raw_writel(dev->reg_shadow[reg_index],
dev->reg_base_addr + dev->reg_offsets[reg_index]);
}
static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
unsigned int reg_index)
{
dev->reg_shadow[reg_index] =
__raw_readl(dev->reg_base_addr +
dev->reg_offsets[reg_index]);
}
static inline uint32_t msm_spm_drv_get_num_spm_entry(
struct msm_spm_driver_data *dev)
{
if (!dev)
return -ENODEV;
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID);
return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 24) & 0xFF;
}
static inline void msm_spm_drv_set_start_addr(
struct msm_spm_driver_data *dev, uint32_t ctl)
{
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] = ctl;
}
static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID);
return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 2) & 0x1;
}
static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
uint32_t vlevel, uint32_t vctl_port)
{
unsigned int pmic_data = 0;
pmic_data |= vlevel;
pmic_data |= (vctl_port & 0x7) << 16;
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0x700FF;
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= pmic_data;
dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_DATA_3] &= ~0x700FF;
dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_DATA_3] |= pmic_data;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL);
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_PMIC_DATA_3);
}
static inline uint32_t msm_spm_drv_get_num_pmic_data(
struct msm_spm_driver_data *dev)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID);
mb(); /* Ensure we flush */
return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 4) & 0x7;
}
static inline uint32_t msm_spm_drv_get_sts_pmic_state(
struct msm_spm_driver_data *dev)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS);
return (dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] >> 16) &
0x03;
}
uint32_t msm_spm_drv_get_sts_curr_pmic_data(
struct msm_spm_driver_data *dev)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS);
return dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] & 0x300FF;
}
static inline void msm_spm_drv_get_saw2_ver(struct msm_spm_driver_data *dev,
uint32_t *major, uint32_t *minor)
{
uint32_t val = 0;
dev->reg_shadow[MSM_SPM_REG_SAW_VERSION] =
__raw_readl(dev->reg_base_addr + dev->ver_reg);
val = dev->reg_shadow[MSM_SPM_REG_SAW_VERSION];
*major = (val >> 28) & 0xF;
*minor = (val >> 16) & 0xFFF;
}
inline int msm_spm_drv_set_spm_enable(
struct msm_spm_driver_data *dev, bool enable)
{
uint32_t value = enable ? 0x01 : 0x00;
if (!dev)
return -EINVAL;
if ((dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] & 0x01) ^ value) {
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] &= ~0x1;
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= value;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
wmb(); /* Ensure we flush */
}
return 0;
}
int msm_spm_drv_get_avs_enable(struct msm_spm_driver_data *dev)
{
if (!dev)
return -EINVAL;
return dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & 0x01;
}
int msm_spm_drv_set_avs_enable(struct msm_spm_driver_data *dev,
bool enable)
{
uint32_t value = enable ? 0x1 : 0x0;
if (!dev)
return -EINVAL;
if ((dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & 0x1) ^ value) {
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~0x1;
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= value;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
}
return 0;
}
int msm_spm_drv_set_avs_limit(struct msm_spm_driver_data *dev,
uint32_t min_lvl, uint32_t max_lvl)
{
uint32_t value = (max_lvl & 0xff) << 16 | (min_lvl & 0xff);
if (!dev)
return -EINVAL;
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_LIMIT] = value;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_LIMIT);
return 0;
}
static int msm_spm_drv_avs_irq_mask(enum msm_spm_avs_irq irq)
{
switch (irq) {
case MSM_SPM_AVS_IRQ_MIN:
return BIT(1);
case MSM_SPM_AVS_IRQ_MAX:
return BIT(2);
default:
return -EINVAL;
}
}
int msm_spm_drv_set_avs_irq_enable(struct msm_spm_driver_data *dev,
enum msm_spm_avs_irq irq, bool enable)
{
int mask = msm_spm_drv_avs_irq_mask(irq);
uint32_t value;
if (!dev)
return -EINVAL;
else if (mask < 0)
return mask;
value = enable ? mask : 0;
if ((dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & mask) ^ value) {
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~mask;
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= value;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
}
return 0;
}
int msm_spm_drv_avs_clear_irq(struct msm_spm_driver_data *dev,
enum msm_spm_avs_irq irq)
{
int mask = msm_spm_drv_avs_irq_mask(irq);
if (!dev)
return -EINVAL;
else if (mask < 0)
return mask;
if (dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & mask) {
/*
* The interrupt status is cleared by disabling and then
* re-enabling the interrupt.
*/
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~mask;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= mask;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
}
return 0;
}
void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
{
int i;
int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
if (!dev) {
__WARN();
return;
}
for (i = 0; i < num_spm_entry; i++) {
__raw_writel(dev->reg_seq_entry_shadow[i],
dev->reg_base_addr
+ dev->reg_offsets[MSM_SPM_REG_SAW_SEQ_ENTRY]
+ 4 * i);
}
mb(); /* Ensure we flush */
}
void dump_regs(struct msm_spm_driver_data *dev, int cpu)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_STS);
mb(); /* Ensure we flush */
pr_err("CPU%d: spm register MSM_SPM_REG_SAW_SPM_STS: 0x%x\n", cpu,
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_STS]);
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
mb(); /* Ensure we flush */
pr_err("CPU%d: spm register MSM_SPM_REG_SAW_SPM_CTL: 0x%x\n", cpu,
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL]);
}
int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
uint8_t *cmd, uint32_t *offset)
{
uint32_t cmd_w;
uint32_t offset_w = *offset / 4;
uint8_t last_cmd;
if (!cmd)
return -EINVAL;
while (1) {
int i;
cmd_w = 0;
last_cmd = 0;
cmd_w = dev->reg_seq_entry_shadow[offset_w];
for (i = (*offset % 4); i < 4; i++) {
last_cmd = *(cmd++);
cmd_w |= last_cmd << (i * 8);
(*offset)++;
if (last_cmd == 0x0f)
break;
}
dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
if (last_cmd == 0x0f)
break;
}
return 0;
}
int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
uint32_t ctl)
{
/* SPM is configured to reset start address to zero after end of Program
*/
if (!dev)
return -EINVAL;
msm_spm_drv_set_start_addr(dev, ctl);
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
wmb(); /* Ensure we flush */
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__,
dev->reg_offsets[i], dev->reg_shadow[i]);
}
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_STS);
return 0;
}
uint32_t msm_spm_drv_get_vdd(struct msm_spm_driver_data *dev)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS);
return dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] & 0xFF;
}
#ifdef CONFIG_MSM_AVS_HW
static bool msm_spm_drv_is_avs_enabled(struct msm_spm_driver_data *dev)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
return dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & BIT(0);
}
static void msm_spm_drv_disable_avs(struct msm_spm_driver_data *dev)
{
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~BIT(27);
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
}
static void msm_spm_drv_enable_avs(struct msm_spm_driver_data *dev)
{
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= BIT(27);
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
}
static void msm_spm_drv_set_avs_vlevel(struct msm_spm_driver_data *dev,
unsigned int vlevel)
{
vlevel &= 0x3f;
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~0x7efc00;
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= ((vlevel - 4) << 10);
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= (vlevel << 17);
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
}
#else
static bool msm_spm_drv_is_avs_enabled(struct msm_spm_driver_data *dev)
{
return false;
}
static void msm_spm_drv_disable_avs(struct msm_spm_driver_data *dev) { }
static void msm_spm_drv_enable_avs(struct msm_spm_driver_data *dev) { }
static void msm_spm_drv_set_avs_vlevel(struct msm_spm_driver_data *dev,
unsigned int vlevel)
{
}
#endif
static inline int msm_spm_drv_validate_data(struct msm_spm_driver_data *dev,
unsigned int vlevel, int vctl_port)
{
int timeout_us = dev->vctl_timeout_us;
uint32_t new_level;
/* Confirm the voltage we set was what hardware sent and
* FSM is idle.
*/
do {
udelay(1);
new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
/**
* VCTL_PORT has to be 0, for vlevel to be updated.
* If port is not 0, check for PMIC_STATE only.
*/
if (((new_level & 0x30000) == MSM_SPM_PMIC_STATE_IDLE) &&
(vctl_port || ((new_level & 0xFF) == vlevel)))
break;
} while (--timeout_us);
if (!timeout_us) {
pr_err("Wrong level %#x\n", new_level);
return -EIO;
}
if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
pr_info("%s: done, remaining timeout %u us\n",
__func__, timeout_us);
return 0;
}
int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
{
uint32_t vlevel_set = vlevel;
bool avs_enabled;
int ret = 0;
if (!dev)
return -EINVAL;
avs_enabled = msm_spm_drv_is_avs_enabled(dev);
if (!msm_spm_pmic_arb_present(dev))
return -ENODEV;
if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
pr_info("%s: requesting vlevel %#x\n", __func__, vlevel);
if (avs_enabled)
msm_spm_drv_disable_avs(dev);
if (dev->vctl_port_ub >= 0) {
/**
* VCTL can send 8bit voltage level at once.
* Send lower 8bit first, vlevel change happens
* when upper 8bit is sent.
*/
vlevel = vlevel_set & 0xFF;
}
/* Kick the state machine back to idle */
dev->reg_shadow[MSM_SPM_REG_SAW_RST] = 1;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_RST);
msm_spm_drv_set_vctl2(dev, vlevel, dev->vctl_port);
ret = msm_spm_drv_validate_data(dev, vlevel, dev->vctl_port);
if (ret)
goto set_vdd_bail;
if (dev->vctl_port_ub >= 0) {
/* Send upper 8bit of voltage level */
vlevel = (vlevel_set >> 8) & 0xFF;
/* Kick the state machine back to idle */
dev->reg_shadow[MSM_SPM_REG_SAW_RST] = 1;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_RST);
/*
* Steps for sending for vctl port other than '0'
* Write VCTL register with pmic data and address index
* Perform system barrier
* Wait for 1us
* Read PMIC_STS register to make sure operation is complete
*/
msm_spm_drv_set_vctl2(dev, vlevel, dev->vctl_port_ub);
mb(); /* To make sure data is sent before checking status */
ret = msm_spm_drv_validate_data(dev, vlevel, dev->vctl_port_ub);
if (ret)
goto set_vdd_bail;
}
/* Set AVS min/max */
if (avs_enabled) {
msm_spm_drv_set_avs_vlevel(dev, vlevel_set);
msm_spm_drv_enable_avs(dev);
}
return ret;
set_vdd_bail:
if (avs_enabled)
msm_spm_drv_enable_avs(dev);
pr_err("%s: failed %#x vlevel setting in timeout %uus\n",
__func__, vlevel_set, dev->vctl_timeout_us);
return -EIO;
}
static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev,
enum msm_spm_pmic_port port)
{
int index = -1;
switch (port) {
case MSM_SPM_PMIC_VCTL_PORT:
index = dev->vctl_port;
break;
case MSM_SPM_PMIC_PHASE_PORT:
index = dev->phase_port;
break;
case MSM_SPM_PMIC_PFM_PORT:
index = dev->pfm_port;
break;
default:
break;
}
return index;
}
int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
enum msm_spm_pmic_port port, unsigned int data)
{
unsigned int pmic_data = 0;
unsigned int timeout_us = 0;
int index = 0;
if (!msm_spm_pmic_arb_present(dev))
return -ENODEV;
index = msm_spm_drv_get_pmic_port(dev, port);
if (index < 0)
return -ENODEV;
pmic_data |= data & 0xFF;
pmic_data |= (index & 0x7) << 16;
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0x700FF;
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= pmic_data;
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL);
mb(); /* Ensure we flush */
timeout_us = dev->vctl_timeout_us;
/**
* Confirm the pmic data set was what hardware sent by
* checking the PMIC FSM state.
* We cannot use the sts_pmic_data and check it against
* the value like we do fot set_vdd, since the PMIC_STS
* is only updated for SAW_VCTL sent with port index 0.
*/
do {
if (msm_spm_drv_get_sts_pmic_state(dev) ==
MSM_SPM_PMIC_STATE_IDLE)
break;
udelay(1);
} while (--timeout_us);
if (!timeout_us) {
pr_err("%s: failed, remaining timeout %u us, data %d\n",
__func__, timeout_us, data);
return -EIO;
}
return 0;
}
void msm_spm_drv_reinit(struct msm_spm_driver_data *dev, bool seq_write)
{
int i;
if (seq_write)
msm_spm_drv_flush_seq_entry(dev);
for (i = 0; i < MSM_SPM_REG_SAW_PMIC_DATA_0 + num_pmic_data; i++)
msm_spm_drv_load_shadow(dev, i);
for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
msm_spm_drv_load_shadow(dev, i);
}
int msm_spm_drv_reg_init(struct msm_spm_driver_data *dev,
struct msm_spm_platform_data *data)
{
int i;
bool found = false;
dev->ver_reg = data->ver_reg;
dev->reg_base_addr = data->reg_base_addr;
msm_spm_drv_get_saw2_ver(dev, &dev->major, &dev->minor);
for (i = 0; i < ARRAY_SIZE(saw2_info); i++)
if (dev->major == saw2_info[i].major &&
dev->minor == saw2_info[i].minor) {
pr_debug("%s: Version found\n",
saw2_info[i].ver_name);
dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr;
found = true;
break;
}
if (!found) {
pr_err("%s: No SAW version found\n", __func__);
WARN_ON(!found);
}
return 0;
}
void msm_spm_drv_upd_reg_shadow(struct msm_spm_driver_data *dev, int id,
int val)
{
dev->reg_shadow[id] = val;
msm_spm_drv_flush_shadow(dev, id);
/* Complete the above writes before other accesses */
mb();
}
int msm_spm_drv_init(struct msm_spm_driver_data *dev,
struct msm_spm_platform_data *data)
{
int num_spm_entry;
if (!dev || !data)
return -ENODEV;
dev->vctl_port = data->vctl_port;
dev->vctl_port_ub = data->vctl_port_ub;
dev->phase_port = data->phase_port;
dev->pfm_port = data->pfm_port;
dev->reg_base_addr = data->reg_base_addr;
memcpy(dev->reg_shadow, data->reg_init_values,
sizeof(data->reg_init_values));
dev->vctl_timeout_us = data->vctl_timeout_us;
if (!num_pmic_data)
num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
dev->reg_seq_entry_shadow =
kcalloc(num_spm_entry, sizeof(*dev->reg_seq_entry_shadow),
GFP_KERNEL);
if (!dev->reg_seq_entry_shadow)
return -ENOMEM;
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,131 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2011-2017, 2020-2021, The Linux Foundation. All rights reserved.
*/
#ifndef __ARCH_ARM_MACH_MSM_SPM_DEVICES_H
#define __ARCH_ARM_MACH_MSM_SPM_DEVICES_H
#include <soc/qcom/spm.h>
enum {
MSM_SPM_REG_SAW_CFG,
MSM_SPM_REG_SAW_AVS_CTL,
MSM_SPM_REG_SAW_AVS_HYSTERESIS,
MSM_SPM_REG_SAW_SPM_CTL,
MSM_SPM_REG_SAW_PMIC_DLY,
MSM_SPM_REG_SAW_AVS_LIMIT,
MSM_SPM_REG_SAW_AVS_DLY,
MSM_SPM_REG_SAW_SPM_DLY,
MSM_SPM_REG_SAW_PMIC_DATA_0,
MSM_SPM_REG_SAW_PMIC_DATA_1,
MSM_SPM_REG_SAW_PMIC_DATA_2,
MSM_SPM_REG_SAW_PMIC_DATA_3,
MSM_SPM_REG_SAW_PMIC_DATA_4,
MSM_SPM_REG_SAW_PMIC_DATA_5,
MSM_SPM_REG_SAW_PMIC_DATA_6,
MSM_SPM_REG_SAW_PMIC_DATA_7,
MSM_SPM_REG_SAW_RST,
MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW_RST,
MSM_SPM_REG_SAW_ID,
MSM_SPM_REG_SAW_SECURE,
MSM_SPM_REG_SAW_STS0,
MSM_SPM_REG_SAW_STS1,
MSM_SPM_REG_SAW_STS2,
MSM_SPM_REG_SAW_VCTL,
MSM_SPM_REG_SAW_SEQ_ENTRY,
MSM_SPM_REG_SAW_SPM_STS,
MSM_SPM_REG_SAW_AVS_STS,
MSM_SPM_REG_SAW_PMIC_STS,
MSM_SPM_REG_SAW_VERSION,
MSM_SPM_REG_NR,
};
struct msm_spm_seq_entry {
uint32_t mode;
uint8_t *cmd;
uint32_t ctl;
};
struct msm_spm_platform_data {
void __iomem *reg_base_addr;
uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
uint32_t ver_reg;
uint32_t vctl_port;
int vctl_port_ub;
uint32_t phase_port;
uint32_t pfm_port;
uint8_t awake_vlevel;
uint32_t vctl_timeout_us;
uint32_t avs_timeout_us;
uint32_t num_modes;
struct msm_spm_seq_entry *modes;
};
enum msm_spm_pmic_port {
MSM_SPM_PMIC_VCTL_PORT,
MSM_SPM_PMIC_PHASE_PORT,
MSM_SPM_PMIC_PFM_PORT,
};
struct msm_spm_driver_data {
uint32_t major;
uint32_t minor;
uint32_t ver_reg;
uint32_t vctl_port;
int vctl_port_ub;
uint32_t phase_port;
uint32_t pfm_port;
void __iomem *reg_base_addr;
uint32_t vctl_timeout_us;
uint32_t avs_timeout_us;
uint32_t reg_shadow[MSM_SPM_REG_NR];
uint32_t *reg_seq_entry_shadow;
uint32_t *reg_offsets;
};
int msm_spm_drv_init(struct msm_spm_driver_data *dev,
struct msm_spm_platform_data *data);
int msm_spm_drv_reg_init(struct msm_spm_driver_data *dev,
struct msm_spm_platform_data *data);
void msm_spm_drv_reinit(struct msm_spm_driver_data *dev, bool seq);
int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
uint32_t ctl);
int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev,
unsigned int vlevel);
void dump_regs(struct msm_spm_driver_data *dev, int cpu);
uint32_t msm_spm_drv_get_sts_curr_pmic_data(
struct msm_spm_driver_data *dev);
int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
uint8_t *cmd, uint32_t *offset);
void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);
int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
bool enable);
int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
enum msm_spm_pmic_port port, unsigned int data);
int msm_spm_drv_set_avs_limit(struct msm_spm_driver_data *dev,
uint32_t min_lvl, uint32_t max_lvl);
int msm_spm_drv_set_avs_enable(struct msm_spm_driver_data *dev,
bool enable);
int msm_spm_drv_get_avs_enable(struct msm_spm_driver_data *dev);
int msm_spm_drv_set_avs_irq_enable(struct msm_spm_driver_data *dev,
enum msm_spm_avs_irq irq, bool enable);
int msm_spm_drv_avs_clear_irq(struct msm_spm_driver_data *dev,
enum msm_spm_avs_irq irq);
void msm_spm_reinit(void);
int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
void msm_spm_drv_upd_reg_shadow(struct msm_spm_driver_data *dev, int id,
int val);
uint32_t msm_spm_drv_get_vdd(struct msm_spm_driver_data *dev);
#endif

173
include/soc/qcom/spm.h Normal file
View File

@ -0,0 +1,173 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2010-2017, 2020-2021, The Linux Foundation. All rights reserved.
*/
#ifndef __ARCH_ARM_MACH_MSM_SPM_H
#define __ARCH_ARM_MACH_MSM_SPM_H
enum {
MSM_SPM_MODE_DISABLED,
MSM_SPM_MODE_CLOCK_GATING,
MSM_SPM_MODE_RETENTION,
MSM_SPM_MODE_GDHS,
MSM_SPM_MODE_POWER_COLLAPSE,
MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE,
MSM_SPM_MODE_FASTPC,
MSM_SPM_MODE_NR
};
enum msm_spm_avs_irq {
MSM_SPM_AVS_IRQ_MIN,
MSM_SPM_AVS_IRQ_MAX,
};
struct msm_spm_device;
struct device_node;
#if defined(CONFIG_MSM_SPM)
int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm);
void msm_spm_set_rpm_hs(bool allow_rpm_hs);
int msm_spm_probe_done(void);
int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel);
int msm_spm_get_vdd(unsigned int cpu);
int msm_spm_turn_on_cpu_rail(struct device_node *l2ccc_node,
unsigned int val, int cpu, int vctl_offset);
struct msm_spm_device *msm_spm_get_device_by_name(const char *name);
int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
unsigned int mode, bool notify_rpm);
int msm_spm_config_low_power_mode_addr(struct msm_spm_device *dev,
unsigned int mode, bool notify_rpm);
int msm_spm_device_init(void);
bool msm_spm_is_mode_avail(unsigned int mode);
void msm_spm_dump_regs(unsigned int cpu);
int msm_spm_is_avs_enabled(unsigned int cpu);
int msm_spm_avs_enable(unsigned int cpu);
int msm_spm_avs_disable(unsigned int cpu);
int msm_spm_avs_set_limit(unsigned int cpu, uint32_t min_lvl,
uint32_t max_lvl);
int msm_spm_avs_enable_irq(unsigned int cpu, enum msm_spm_avs_irq irq);
int msm_spm_avs_disable_irq(unsigned int cpu, enum msm_spm_avs_irq irq);
int msm_spm_avs_clear_irq(unsigned int cpu, enum msm_spm_avs_irq irq);
#if defined(CONFIG_MSM_L2_SPM)
/* Public functions */
int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt);
int msm_spm_enable_fts_lpm(int cpu, uint32_t mode);
#else
static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
{
return -ENODEV;
}
static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
{
return -ENODEV;
}
#endif /* defined(CONFIG_MSM_L2_SPM) */
#else /* defined(CONFIG_MSM_SPM) */
static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
{
return -ENODEV;
}
static inline void msm_spm_set_rpm_hs(bool allow_rpm_hs) {}
static inline int msm_spm_probe_done(void)
{
return -ENODEV;
}
static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
{
return -ENODEV;
}
static inline int msm_spm_get_vdd(unsigned int cpu)
{
return 0;
}
static inline int msm_spm_turn_on_cpu_rail(struct device_node *l2ccc_node,
unsigned int val, int cpu, int vctl_offset)
{
return -ENODEV;
}
static inline int msm_spm_device_init(void)
{
return -ENODEV;
}
static inline void msm_spm_dump_regs(unsigned int cpu)
{ }
static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
unsigned int mode, bool notify_rpm)
{
return -ENODEV;
}
static inline int msm_spm_config_low_power_mode_addr(
struct msm_spm_device *dev, unsigned int mode, bool notify_rpm)
{
return -ENODEV;
}
static inline struct msm_spm_device *msm_spm_get_device_by_name(
const char *name)
{
return NULL;
}
static inline bool msm_spm_is_mode_avail(unsigned int mode)
{
return false;
}
static inline int msm_spm_is_avs_enabled(unsigned int cpu)
{
return -ENODEV;
}
static inline int msm_spm_avs_enable(unsigned int cpu)
{
return -ENODEV;
}
static inline int msm_spm_avs_disable(unsigned int cpu)
{
return -ENODEV;
}
static inline int msm_spm_avs_set_limit(unsigned int cpu, uint32_t min_lvl,
uint32_t max_lvl)
{
return -ENODEV;
}
static inline int msm_spm_avs_enable_irq(unsigned int cpu,
enum msm_spm_avs_irq irq)
{
return -ENODEV;
}
static inline int msm_spm_avs_disable_irq(unsigned int cpu,
enum msm_spm_avs_irq irq)
{
return -ENODEV;
}
static inline int msm_spm_avs_clear_irq(unsigned int cpu,
enum msm_spm_avs_irq irq)
{
return -ENODEV;
}
#endif /* defined (CONFIG_MSM_SPM) */
#endif /* __ARCH_ARM_MACH_MSM_SPM_H */