Merge "drivers: soc: qcom: pm: Add legacy PM and Warmboot set address API support"

This commit is contained in:
qctecmdr 2021-03-17 06:25:17 -07:00 • committed by Gerrit - the friendly Code Review server
commit d7e2efd740
11 changed files with 3090 additions and 16 deletions

View file

@ -19,13 +19,13 @@
#include <linux/pm_qos.h>
#include <linux/of_platform.h>
#include <linux/smp.h>
#include <linux/remote_spinlock.h>
#include <linux/msm_remote_spinlock.h>
#include <linux/dma-mapping.h>
#include <linux/coresight-cti.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include <linux/cpu_pm.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <soc/qcom/spm.h>
#include <soc/qcom/pm-legacy.h>
#include <soc/qcom/rpm-notifier.h>
@ -48,10 +48,22 @@
#include <soc/qcom/minidump.h>
#define SCLK_HZ (32768)
#define SCM_HANDOFF_LOCK_ID "S:7"
#define PSCI_POWER_STATE(reset) (reset << 30)
#define PSCI_AFFINITY_LEVEL(lvl) ((lvl & 0x3) << 24)
static remote_spinlock_t scm_handoff_lock;
#define MUTEX_NUM_PID 128
#define MUTEX_TID_START MUTEX_NUM_PID
#define SCM_HANDOFF_LOCK_ID 7
/* sfpb implementation for hardware spinlock usage */
static phys_addr_t reg_base;
static uint32_t reg_size;
static uint32_t lock_size;
static void __iomem *hw_mutex_reg_base;
struct mutex_reg {
uint32_t regaddr;
};
enum {
MSM_LPM_LVL_DBG_SUSPEND_LIMITS = BIT(0),
@ -1278,13 +1290,38 @@ static const struct platform_suspend_ops lpm_suspend_ops = {
.wake = lpm_suspend_wake,
};
static int init_hw_mutex(struct device_node *node)
{
struct resource r;
int rc;
static uint32_t lock_count;
rc = of_address_to_resource(node, 0, &r);
if (rc) {
pr_err("Failed to get resource\n");
return 1;
}
rc = of_property_read_u32(node, "qcom,num-locks", &lock_count);
if (rc) {
pr_err("Failed to get num-locks property\n");
return 1;
}
reg_base = r.start;
reg_size = (uint32_t)(resource_size(&r));
lock_size = reg_size / lock_count;
return 0;
}
static int lpm_probe(struct platform_device *pdev)
{
int ret;
int size;
struct kobject *module_kobj = NULL;
struct md_region md_entry;
struct device_node *node;
get_online_cpus();
lpm_root_node = lpm_of_parse_cluster(pdev);
@ -1305,14 +1342,6 @@ static int lpm_probe(struct platform_device *pdev)
*/
suspend_set_ops(&lpm_suspend_ops);
hrtimer_init(&lpm_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ret = remote_spin_lock_init(&scm_handoff_lock, SCM_HANDOFF_LOCK_ID);
if (ret) {
pr_err("%s: Failed initializing scm_handoff_lock (%d)\n",
__func__, ret);
put_online_cpus();
return ret;
}
size = num_dbg_elements * sizeof(struct lpm_debug);
lpm_debug = dma_alloc_coherent(&pdev->dev, size,
&lpm_debug_phys, GFP_KERNEL);
@ -1354,6 +1383,28 @@ static int lpm_probe(struct platform_device *pdev)
if (msm_minidump_add_region(&md_entry))
pr_info("Failed to add lpm_debug in Minidump\n");
node = of_find_node_by_name(NULL, "qcom,ipc-spinlock");
if (!node) {
pr_err("Failed to find ipc-spinlock node\n");
ret = -ENODEV;
goto failed;
}
if (init_hw_mutex(node)) {
ret = -EINVAL;
of_node_put(node);
goto failed;
}
hw_mutex_reg_base = ioremap(reg_base, reg_size);
if (!hw_mutex_reg_base) {
pr_err("ioremap failed\n");
ret = -ENOMEM;
of_node_put(node);
goto failed;
}
of_node_put(node);
return 0;
failed:
free_cluster_node(lpm_root_node);
@ -1389,11 +1440,24 @@ fail:
}
late_initcall(lpm_levels_module_init);
static void mutex_reg_write(uint32_t tid)
{
struct mutex_reg *lock;
lock = hw_mutex_reg_base + (SCM_HANDOFF_LOCK_ID * lock_size);
do {
writel_relaxed(tid, lock);
/* barrier for proper semantics */
smp_mb();
} while (readl_relaxed(lock) != tid);
}
enum msm_pm_l2_scm_flag lpm_cpu_pre_pc_cb(unsigned int cpu)
{
struct lpm_cluster *cluster = per_cpu(cpu_cluster, cpu);
enum msm_pm_l2_scm_flag retflag = MSM_SCM_L2_ON;
uint32_t tid;
/*
* No need to acquire the lock if probe isn't completed yet
* In the event of the hotplug happening before lpm probe, we want to
@ -1434,8 +1498,8 @@ unlock_and_return:
update_debug_pc_event(PRE_PC_CB, retflag, 0xdeadbeef, 0xdeadbeef,
0xdeadbeef);
trace_pre_pc_cb(retflag);
remote_spin_lock_rlock_id(&scm_handoff_lock,
REMOTE_SPINLOCK_TID_START + cpu);
tid = MUTEX_TID_START + cpu;
mutex_reg_write(tid);
spin_unlock(&cluster->sync_lock);
return retflag;
}

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,8 @@ 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_PM_LEGACY) += pm-boot.o msm-pm.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

21
drivers/soc/qcom/idle.h Normal file
View file

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2007-2009,2012-2014, 2018-2019, 2021 The Linux Foundation.
*/
#ifndef _ARCH_ARM_MACH_MSM_IDLE_H_
#define _ARCH_ARM_MACH_MSM_IDLE_H_
#define MAX_CPUS_PER_CLUSTER 4
#define MAX_NUM_CLUSTER 4
#ifndef __ASSEMBLY__
#if defined(CONFIG_CPU_V7) || defined(CONFIG_ARM64)
extern unsigned long msm_pm_boot_vector[MAX_NUM_CLUSTER * MAX_CPUS_PER_CLUSTER];
void msm_pm_boot_entry(void);
#else
static inline void msm_pm_boot_entry(void) {}
#endif
#endif
#endif

819
drivers/soc/qcom/msm-pm.c Normal file
View file

@ -0,0 +1,819 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2010-2019, 2021 The Linux Foundation. All rights reserved.
*/
/* Copyright (c) 2010-2019, 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/debugfs.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/io.h>
#include <linux/ktime.h>
#include <linux/smp.h>
#include <linux/tick.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/qcom_scm.h>
#include <linux/uaccess.h>
#include <linux/dma-mapping.h>
#include <soc/qcom/spm.h>
#include <soc/qcom/pm-legacy.h>
#include <asm/suspend.h>
#include <asm/cacheflush.h>
#include <asm/cputype.h>
#include <asm/system_misc.h>
#ifdef CONFIG_VFP
#include <asm/vfp.h>
#endif
#include <soc/qcom/jtag.h>
#include "pm-boot.h"
#include "idle.h"
#define SCM_CMD_TERMINATE_PC (0x2)
#define SCM_CMD_CORE_HOTPLUGGED (0x10)
#define SCM_FLUSH_FLAG_MASK (0x3)
#define SCLK_HZ (32768)
#define MAX_BUF_SIZE 1024
static int msm_pm_debug_mask = 1;
module_param_named(
debug_mask, msm_pm_debug_mask, int, 0664
);
enum {
MSM_PM_DEBUG_SUSPEND = BIT(0),
MSM_PM_DEBUG_POWER_COLLAPSE = BIT(1),
MSM_PM_DEBUG_SUSPEND_LIMITS = BIT(2),
MSM_PM_DEBUG_CLOCK = BIT(3),
MSM_PM_DEBUG_RESET_VECTOR = BIT(4),
MSM_PM_DEBUG_IDLE = BIT(5),
MSM_PM_DEBUG_IDLE_LIMITS = BIT(6),
MSM_PM_DEBUG_HOTPLUG = BIT(7),
};
enum msm_pc_count_offsets {
MSM_PC_ENTRY_COUNTER,
MSM_PC_EXIT_COUNTER,
MSM_PC_FALLTHRU_COUNTER,
MSM_PC_UNUSED,
MSM_PC_NUM_COUNTERS,
};
static bool msm_pm_ldo_retention_enabled = true;
static bool msm_pm_tz_flushes_cache;
static bool msm_pm_ret_no_pll_switch;
static bool msm_no_ramp_down_pc;
static struct msm_pm_sleep_status_data *msm_pm_slp_sts;
static DEFINE_PER_CPU(struct clk *, cpu_clks);
static struct clk *l2_clk;
static long *msm_pc_debug_counters;
static cpumask_t retention_cpus;
static DEFINE_SPINLOCK(retention_lock);
static DEFINE_MUTEX(msm_pc_debug_mutex);
static bool msm_pm_is_L1_writeback(void)
{
u32 cache_id = 0;
#if defined(CONFIG_CPU_V7)
u32 sel = 0;
asm volatile ("mcr p15, 2, %[ccselr], c0, c0, 0\n\t"
"isb\n\t"
"mrc p15, 1, %[ccsidr], c0, c0, 0\n\t"
: [ccsidr]"=r" (cache_id)
: [ccselr]"r" (sel)
);
return cache_id & BIT(30);
#elif defined(CONFIG_ARM64)
u32 sel = 0;
asm volatile("msr csselr_el1, %[ccselr]\n\t"
"isb\n\t"
"mrs %[ccsidr],ccsidr_el1\n\t"
: [ccsidr]"=r" (cache_id)
: [ccselr]"r" (sel)
);
return cache_id & BIT(30);
#else
#error No valid CPU arch selected
#endif
}
static bool msm_pm_swfi(bool from_idle)
{
msm_arch_idle();
return true;
}
static bool msm_pm_retention(bool from_idle)
{
int ret = 0;
unsigned int cpu = smp_processor_id();
struct clk *cpu_clk = per_cpu(cpu_clks, cpu);
spin_lock(&retention_lock);
if (!msm_pm_ldo_retention_enabled)
goto bailout;
cpumask_set_cpu(cpu, &retention_cpus);
spin_unlock(&retention_lock);
if (!msm_pm_ret_no_pll_switch)
clk_disable(cpu_clk);
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_RETENTION, false);
WARN_ON(ret);
msm_arch_idle();
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
WARN_ON(ret);
if (!msm_pm_ret_no_pll_switch)
if (clk_enable(cpu_clk))
pr_err("%s(): Error restore cpu clk\n", __func__);
spin_lock(&retention_lock);
cpumask_clear_cpu(cpu, &retention_cpus);
bailout:
spin_unlock(&retention_lock);
return true;
}
static inline void msm_pc_inc_debug_count(uint32_t cpu,
enum msm_pc_count_offsets offset)
{
int cntr_offset;
uint32_t cluster_id = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1);
uint32_t cpu_id = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 0);
if (cluster_id >= MAX_NUM_CLUSTER || cpu_id >= MAX_CPUS_PER_CLUSTER)
WARN_ON(cpu);
cntr_offset = (cluster_id * MAX_CPUS_PER_CLUSTER * MSM_PC_NUM_COUNTERS)
+ (cpu_id * MSM_PC_NUM_COUNTERS) + offset;
if (!msm_pc_debug_counters)
return;
msm_pc_debug_counters[cntr_offset]++;
}
static bool msm_pm_pc_hotplug(void)
{
uint32_t cpu = smp_processor_id();
enum msm_pm_l2_scm_flag flag;
flag = lpm_cpu_pre_pc_cb(cpu);
if (!msm_pm_tz_flushes_cache) {
if (flag == MSM_SCM_L2_OFF)
flush_cache_all();
else if (msm_pm_is_L1_writeback())
flush_cache_louis();
}
msm_pc_inc_debug_count(cpu, MSM_PC_ENTRY_COUNTER);
qcom_scm_cpu_power_down(SCM_CMD_CORE_HOTPLUGGED |
(flag & SCM_FLUSH_FLAG_MASK));
/* Should not return here */
msm_pc_inc_debug_count(cpu, MSM_PC_FALLTHRU_COUNTER);
return false;
}
static bool msm_pm_fastpc(bool from_idle)
{
int ret = 0;
unsigned int cpu = smp_processor_id();
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_FASTPC, false);
WARN_ON(ret);
if (from_idle || cpu_online(cpu))
msm_arch_idle();
else
msm_pm_pc_hotplug();
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
WARN_ON(ret);
return true;
}
int msm_pm_collapse(unsigned long unused)
{
uint32_t cpu = smp_processor_id();
enum msm_pm_l2_scm_flag flag;
flag = lpm_cpu_pre_pc_cb(cpu);
if (!msm_pm_tz_flushes_cache) {
if (flag == MSM_SCM_L2_OFF)
flush_cache_all();
else if (msm_pm_is_L1_writeback())
flush_cache_louis();
}
msm_pc_inc_debug_count(cpu, MSM_PC_ENTRY_COUNTER);
qcom_scm_cpu_power_down(flag & SCM_FLUSH_FLAG_MASK);
msm_pc_inc_debug_count(cpu, MSM_PC_FALLTHRU_COUNTER);
return 0;
}
EXPORT_SYMBOL(msm_pm_collapse);
static bool __ref msm_pm_spm_power_collapse(
unsigned int cpu, int mode, bool from_idle, bool notify_rpm)
{
void *entry;
bool collapsed = false;
int ret;
bool save_cpu_regs = (cpu_online(cpu) || from_idle);
if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)
pr_info("CPU%u: %s: notify_rpm %d\n",
cpu, __func__, (int) notify_rpm);
ret = msm_spm_set_low_power_mode(mode, notify_rpm);
WARN_ON(ret);
entry = save_cpu_regs ? cpu_resume : msm_secondary_startup;
msm_pm_boot_config_before_pc(cpu, virt_to_phys(entry));
if (MSM_PM_DEBUG_RESET_VECTOR & msm_pm_debug_mask)
pr_info("CPU%u: %s: program vector to %pK\n",
cpu, __func__, entry);
msm_jtag_save_state();
collapsed = save_cpu_regs ?
!cpu_suspend(0, msm_pm_collapse) : msm_pm_pc_hotplug();
msm_jtag_restore_state();
if (collapsed)
local_fiq_enable();
msm_pm_boot_config_after_pc(cpu);
if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)
pr_info("CPU%u: %s: msm_pm_collapse returned, collapsed %d\n",
cpu, __func__, collapsed);
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
WARN_ON(ret);
return collapsed;
}
static bool msm_pm_power_collapse_standalone(
bool from_idle)
{
unsigned int cpu = smp_processor_id();
bool collapsed;
collapsed = msm_pm_spm_power_collapse(cpu,
MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE,
from_idle, false);
return collapsed;
}
static int ramp_down_last_cpu(int cpu)
{
struct clk *cpu_clk = per_cpu(cpu_clks, cpu);
clk_disable(cpu_clk);
clk_disable(l2_clk);
return 0;
}
static int ramp_up_first_cpu(int cpu, int saved_rate)
{
struct clk *cpu_clk = per_cpu(cpu_clks, cpu);
int rc = 0;
if (MSM_PM_DEBUG_CLOCK & msm_pm_debug_mask)
pr_info("CPU%u: %s: restore clock rate\n",
cpu, __func__);
clk_enable(l2_clk);
if (cpu_clk) {
int ret = clk_enable(cpu_clk);
if (ret) {
pr_err("%s(): Error restoring cpu clk\n",
__func__);
return ret;
}
}
return rc;
}
static bool msm_pm_power_collapse(bool from_idle)
{
unsigned int cpu = smp_processor_id();
unsigned long saved_acpuclk_rate = 0;
bool collapsed;
if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)
pr_info("CPU%u: %s: idle %d\n",
cpu, __func__, (int)from_idle);
if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)
pr_info("CPU%u: %s: pre power down\n", cpu, __func__);
if (cpu_online(cpu) && !msm_no_ramp_down_pc)
saved_acpuclk_rate = ramp_down_last_cpu(cpu);
collapsed = msm_pm_spm_power_collapse(cpu, MSM_SPM_MODE_POWER_COLLAPSE,
from_idle, true);
if (cpu_online(cpu) && !msm_no_ramp_down_pc)
ramp_up_first_cpu(cpu, saved_acpuclk_rate);
if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)
pr_info("CPU%u: %s: post power up\n", cpu, __func__);
if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask)
pr_info("CPU%u: %s: return\n", cpu, __func__);
return collapsed;
}
/******************************************************************************
* External Idle/Suspend Functions
*****************************************************************************/
static void arch_idle(void) {}
static bool (*execute[MSM_PM_SLEEP_MODE_NR])(bool idle) = {
[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = msm_pm_swfi,
[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] =
msm_pm_power_collapse_standalone,
[MSM_PM_SLEEP_MODE_RETENTION] = msm_pm_retention,
[MSM_PM_SLEEP_MODE_FASTPC] = msm_pm_fastpc,
[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = msm_pm_power_collapse,
};
/**
* msm_cpu_pm_enter_sleep(): Enter a low power mode on current cpu
*
* @mode - sleep mode to enter
* @from_idle - bool to indicate that the mode is exercised during idle/suspend
*
* returns none
*
* The code should be with interrupts disabled and on the core on which the
* low power is to be executed.
*
*/
bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle)
{
bool exit_stat = false;
unsigned int cpu = smp_processor_id();
if ((!from_idle && cpu_online(cpu))
|| (MSM_PM_DEBUG_IDLE & msm_pm_debug_mask))
pr_info("CPU%u:%s mode:%d during %s\n", cpu, __func__,
mode, from_idle ? "idle" : "suspend");
if (execute[mode])
exit_stat = execute[mode](from_idle);
return exit_stat;
}
/**
* msm_pm_wait_cpu_shutdown() - Wait for a core to be power collapsed during
* hotplug
*
* @ cpu - cpu to wait on.
*
* Blocking function call that waits on the core to be power collapsed. This
* function is called from platform_cpu_die to ensure that a core is power
* collapsed before sending the CPU_DEAD notification so the drivers could
* remove the resource votes for this CPU(regulator and clock)
*/
int msm_pm_wait_cpu_shutdown(unsigned int cpu)
{
int timeout = 0;
if (!msm_pm_slp_sts)
return 0;
if (!msm_pm_slp_sts[cpu].base_addr)
return 0;
while (1) {
/*
* Check for the SPM of the core being hotplugged to set
* its sleep state.The SPM sleep state indicates that the
* core has been power collapsed.
*/
int acc_sts = __raw_readl(msm_pm_slp_sts[cpu].base_addr);
if (acc_sts & msm_pm_slp_sts[cpu].mask)
return 0;
udelay(100);
/*
* Dump spm registers for debugging
*/
if (++timeout == 20) {
msm_spm_dump_regs(cpu);
//__WARN_printf(
//"CPU%u didn't collapse in 2ms, sleep status: 0x%x\n",
// cpu, acc_sts);
}
}
return -EBUSY;
}
static void msm_pm_ack_retention_disable(void *data)
{
/*
* This is a NULL function to ensure that the core has woken up
* and is safe to disable retention.
*/
}
/**
* msm_pm_enable_retention() - Disable/Enable retention on all cores
* @enable: Enable/Disable retention
*
*/
void msm_pm_enable_retention(bool enable)
{
if (enable == msm_pm_ldo_retention_enabled)
return;
msm_pm_ldo_retention_enabled = enable;
/*
* If retention is being disabled, wakeup all online core to ensure
* that it isn't executing retention. Offlined cores need not be woken
* up as they enter the deepest sleep mode, namely RPM assited power
* collapse
*/
if (!enable) {
preempt_disable();
smp_call_function_many(&retention_cpus,
msm_pm_ack_retention_disable,
NULL, true);
preempt_enable();
}
}
EXPORT_SYMBOL(msm_pm_enable_retention);
/**
* msm_pm_retention_enabled() - Check if retention is enabled
*
* returns true if retention is enabled
*/
bool msm_pm_retention_enabled(void)
{
return msm_pm_ldo_retention_enabled;
}
EXPORT_SYMBOL(msm_pm_retention_enabled);
static int msm_cpu_status_probe(struct platform_device *pdev)
{
u32 cpu;
int rc;
if (!pdev || !pdev->dev.of_node)
return -EFAULT;
msm_pm_slp_sts = devm_kzalloc(&pdev->dev,
sizeof(*msm_pm_slp_sts) * num_possible_cpus(),
GFP_KERNEL);
if (!msm_pm_slp_sts)
return -ENOMEM;
for_each_possible_cpu(cpu) {
struct device_node *cpun, *node;
char *key;
cpun = of_get_cpu_node(cpu, NULL);
if (!cpun) {
__WARN();
continue;
}
node = of_parse_phandle(cpun, "qcom,sleep-status", 0);
if (!node)
return -ENODEV;
msm_pm_slp_sts[cpu].base_addr = of_iomap(node, 0);
if (!msm_pm_slp_sts[cpu].base_addr) {
pr_err("%s: Can't find base addr\n", __func__);
return -ENODEV;
}
key = "qcom,sleep-status-mask";
rc = of_property_read_u32(node, key, &msm_pm_slp_sts[cpu].mask);
if (rc) {
pr_err("%s: Can't find %s property\n", __func__, key);
iounmap(msm_pm_slp_sts[cpu].base_addr);
return rc;
}
}
return 0;
};
struct msm_pc_debug_counters_buffer {
long *reg;
u32 len;
char buf[MAX_BUF_SIZE];
};
static char *counter_name[MSM_PC_NUM_COUNTERS] = {
"PC Entry Counter",
"Warmboot Entry Counter",
"PC Bailout Counter"
};
static int msm_pc_debug_counters_copy(
struct msm_pc_debug_counters_buffer *data)
{
int j;
u32 stat;
unsigned int cpu;
unsigned int len;
uint32_t cluster_id;
uint32_t cpu_id;
uint32_t offset;
for_each_possible_cpu(cpu) {
len = scnprintf(data->buf + data->len,
sizeof(data->buf)-data->len,
"CPU%d\n", cpu);
cluster_id = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1);
cpu_id = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 0);
offset = (cluster_id * MAX_CPUS_PER_CLUSTER
* MSM_PC_NUM_COUNTERS)
+ (cpu_id * MSM_PC_NUM_COUNTERS);
data->len += len;
for (j = 0; j < MSM_PC_NUM_COUNTERS - 1; j++) {
stat = data->reg[offset + j];
len = scnprintf(data->buf + data->len,
sizeof(data->buf) - data->len,
"\t%s: %d", counter_name[j], stat);
data->len += len;
}
len = scnprintf(data->buf + data->len,
sizeof(data->buf) - data->len,
"\n");
data->len += len;
}
return data->len;
}
static ssize_t msm_pc_debug_counters_file_read(struct file *file,
char __user *bufu, size_t count, loff_t *ppos)
{
struct msm_pc_debug_counters_buffer *data;
ssize_t ret;
mutex_lock(&msm_pc_debug_mutex);
data = file->private_data;
if (!data) {
ret = -EINVAL;
goto exit;
}
if (!bufu) {
ret = -EINVAL;
goto exit;
}
if (!access_ok(bufu, count)) {
ret = -EFAULT;
goto exit;
}
if (*ppos >= data->len && data->len == 0)
data->len = msm_pc_debug_counters_copy(data);
ret = simple_read_from_buffer(bufu, count, ppos,
data->buf, data->len);
exit:
mutex_unlock(&msm_pc_debug_mutex);
return ret;
}
static int msm_pc_debug_counters_file_open(struct inode *inode,
struct file *file)
{
struct msm_pc_debug_counters_buffer *buf;
int ret = 0;
mutex_lock(&msm_pc_debug_mutex);
if (!inode->i_private) {
ret = -EINVAL;
goto exit;
}
file->private_data = kzalloc(
sizeof(struct msm_pc_debug_counters_buffer), GFP_KERNEL);
if (!file->private_data) {
pr_err("%s: ERROR kmalloc failed to allocate %zu bytes\n",
__func__, sizeof(struct msm_pc_debug_counters_buffer));
ret = -ENOMEM;
goto exit;
}
buf = file->private_data;
buf->reg = (long *)inode->i_private;
exit:
mutex_unlock(&msm_pc_debug_mutex);
return ret;
}
static int msm_pc_debug_counters_file_close(struct inode *inode,
struct file *file)
{
mutex_lock(&msm_pc_debug_mutex);
kfree(file->private_data);
mutex_unlock(&msm_pc_debug_mutex);
return 0;
}
static const struct file_operations msm_pc_debug_counters_fops = {
.open = msm_pc_debug_counters_file_open,
.read = msm_pc_debug_counters_file_read,
.release = msm_pc_debug_counters_file_close,
.llseek = no_llseek,
};
static int msm_pm_clk_init(struct platform_device *pdev)
{
bool synced_clocks;
u32 cpu;
char clk_name[] = "cpu??_clk";
char *key;
key = "qcom,saw-turns-off-pll";
if (of_property_read_bool(pdev->dev.of_node, key))
return 0;
key = "qcom,synced-clocks";
synced_clocks = of_property_read_bool(pdev->dev.of_node, key);
for_each_possible_cpu(cpu) {
struct clk *clk;
snprintf(clk_name, sizeof(clk_name), "cpu%d_clk", cpu);
clk = clk_get(&pdev->dev, clk_name);
if (IS_ERR(clk)) {
if (cpu && synced_clocks)
return 0;
clk = NULL;
}
per_cpu(cpu_clks, cpu) = clk;
}
if (synced_clocks)
return 0;
l2_clk = clk_get(&pdev->dev, "l2_clk");
if (IS_ERR(l2_clk))
pr_warn("%s: Could not get l2_clk (-%ld)\n", __func__,
PTR_ERR(l2_clk));
return 0;
}
static int msm_cpu_pm_probe(struct platform_device *pdev)
{
struct dentry *dent = NULL;
struct resource *res = NULL;
int ret = 0;
void __iomem *msm_pc_debug_counters_imem;
char *key;
int alloc_size = (MAX_NUM_CLUSTER * MAX_CPUS_PER_CLUSTER
* MSM_PC_NUM_COUNTERS
* sizeof(*msm_pc_debug_counters));
msm_pc_debug_counters = dma_alloc_coherent(&pdev->dev, alloc_size,
&msm_pc_debug_counters_phys, GFP_KERNEL);
if (msm_pc_debug_counters) {
memset(msm_pc_debug_counters, 0, alloc_size);
dent = debugfs_create_file("pc_debug_counter", 0444, NULL,
msm_pc_debug_counters,
&msm_pc_debug_counters_fops);
if (!dent)
pr_err("%s: ERROR debugfs_create_file failed\n",
__func__);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
goto skip_save_imem;
msm_pc_debug_counters_imem = devm_ioremap(&pdev->dev,
res->start, resource_size(res));
if (msm_pc_debug_counters_imem) {
writel_relaxed(msm_pc_debug_counters_phys,
msm_pc_debug_counters_imem);
/* memory barrier */
mb();
}
} else {
msm_pc_debug_counters = NULL;
msm_pc_debug_counters_phys = 0;
}
skip_save_imem:
if (pdev->dev.of_node) {
key = "qcom,tz-flushes-cache";
msm_pm_tz_flushes_cache =
of_property_read_bool(pdev->dev.of_node, key);
key = "qcom,no-pll-switch-for-retention";
msm_pm_ret_no_pll_switch =
of_property_read_bool(pdev->dev.of_node, key);
ret = msm_pm_clk_init(pdev);
if (ret) {
pr_info("msm_pm_clk_init returned error\n");
return ret;
}
}
if (pdev->dev.of_node)
of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
return ret;
}
static const struct of_device_id msm_cpu_pm_table[] = {
{.compatible = "qcom,pm"},
{},
};
static struct platform_driver msm_cpu_pm_driver = {
.probe = msm_cpu_pm_probe,
.driver = {
.name = "msm-pm",
.of_match_table = msm_cpu_pm_table,
},
};
static int __init msm_pm_debug_counters_init(void)
{
int rc;
rc = platform_driver_register(&msm_cpu_pm_driver);
if (rc)
pr_err("%s(): failed to register driver %s\n", __func__,
msm_cpu_pm_driver.driver.name);
return rc;
}
fs_initcall(msm_pm_debug_counters_init);
#ifdef CONFIG_ARM
static int idle_initialize(void)
{
arm_pm_idle = arch_idle;
return 0;
}
early_initcall(idle_initialize);
#endif

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;
}

View file

@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2011-2014, 2016, 2018-2019, 2021 The Linux Foundation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <asm/cacheflush.h>
#include <asm/cputype.h>
#include <asm/smp_plat.h>
#include <linux/qcom_scm.h>
#include "pm-boot.h"
#include "idle.h"
#define CPU_INDEX(cluster, cpu) (cluster * MAX_CPUS_PER_CLUSTER + cpu)
#define SCM_FLAG_WARMBOOT_MC 0x04
static void (*msm_pm_boot_before_pc)(unsigned int cpu, unsigned long entry);
static void (*msm_pm_boot_after_pc)(unsigned int cpu);
static int msm_pm_tz_boot_init(void)
{
phys_addr_t warmboot_addr = msm_pm_boot_entry;
return qcom_scm_set_warm_boot_addr_mc(warmboot_addr, ~0U, ~0U, ~0U,
SCM_FLAG_WARMBOOT_MC);
}
static void msm_pm_write_boot_vector(unsigned int cpu, unsigned long address)
{
uint32_t clust_id = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1);
uint32_t cpu_id = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 0);
unsigned long *start_address;
unsigned long *end_address;
if (clust_id >= MAX_NUM_CLUSTER || cpu_id >= MAX_CPUS_PER_CLUSTER)
WARN_ON(cpu);
msm_pm_boot_vector[CPU_INDEX(clust_id, cpu_id)] = address;
start_address = &msm_pm_boot_vector[CPU_INDEX(clust_id, cpu_id)];
end_address = &msm_pm_boot_vector[CPU_INDEX(clust_id, cpu_id + 1)];
}
static void msm_pm_config_tz_before_pc(unsigned int cpu,
unsigned long entry)
{
msm_pm_write_boot_vector(cpu, entry);
}
void msm_pm_boot_config_before_pc(unsigned int cpu, unsigned long entry)
{
if (msm_pm_boot_before_pc)
msm_pm_boot_before_pc(cpu, entry);
}
void msm_pm_boot_config_after_pc(unsigned int cpu)
{
if (msm_pm_boot_after_pc)
msm_pm_boot_after_pc(cpu);
}
static int __init msm_pm_boot_init(void)
{
int ret = 0;
ret = msm_pm_tz_boot_init();
msm_pm_boot_before_pc = msm_pm_config_tz_before_pc;
msm_pm_boot_after_pc = NULL;
return ret;
}
late_initcall(msm_pm_boot_init);

View file

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2011-2014, 2018-2019, 2021 The Linux Foundation. All rights reserved.
*/
#ifndef _ARCH_ARM_MACH_MSM_PM_BOOT_H
#define _ARCH_ARM_MACH_MSM_PM_BOOT_H
void msm_pm_boot_config_before_pc(unsigned int cpu, unsigned long entry);
void msm_pm_boot_config_after_pc(unsigned int cpu);
#endif

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 */