From baae1562c69707a369574ffc5c55428a1d74d0e3 Mon Sep 17 00:00:00 2001 From: Hari babu Date: Fri, 25 Oct 2019 20:15:08 +0530 Subject: [PATCH 1/3] cpuidle: lpm_levels: Replace remote spinlock Remove deprecated remote spinlock usage, added changes to write the tid value into mutex lock register. Change-Id: I031566b4038df53a99306c34db427c5929d68483 Signed-off-by: Raghavendra Kakarla Signed-off-by: Hari babu Signed-off-by: Sivasri Kumar Vanka Signed-off-by: Haribabu Gattem Signed-off-by: Tushar Nimkar --- drivers/cpuidle/lpm-levels-legacy.c | 96 ++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 16 deletions(-) diff --git a/drivers/cpuidle/lpm-levels-legacy.c b/drivers/cpuidle/lpm-levels-legacy.c index 1f727795698a..4759812c2390 100644 --- a/drivers/cpuidle/lpm-levels-legacy.c +++ b/drivers/cpuidle/lpm-levels-legacy.c @@ -19,13 +19,13 @@ #include #include #include -#include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -48,10 +48,22 @@ #include #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; } From e48c59bf5aba2d0e5d8461ab11a1a2f17024b90c Mon Sep 17 00:00:00 2001 From: Raghavendra Kakarla Date: Thu, 16 Nov 2017 12:28:54 +0530 Subject: [PATCH 2/3] drivers: soc: qcom: Enable SPM driver Enables the support SPM and AVS wrapper hardware. Change-Id: I2525d5fadf5be318f55ce722c26e598640af926f Signed-off-by: Raghavendra Kakarla Signed-off-by: Tushar Nimkar --- drivers/soc/qcom/Kconfig | 16 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/msm-spm.c | 762 ++++++++++++++++++++++++ drivers/soc/qcom/spm_devices.c | 1002 ++++++++++++++++++++++++++++++++ drivers/soc/qcom/spm_driver.h | 131 +++++ include/soc/qcom/spm.h | 173 ++++++ 6 files changed, 2085 insertions(+) create mode 100644 drivers/soc/qcom/msm-spm.c create mode 100644 drivers/soc/qcom/spm_devices.c create mode 100644 drivers/soc/qcom/spm_driver.h create mode 100644 include/soc/qcom/spm.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 12c720236aaf..534ddd0e4168 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -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 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 4793fa130fbe..177a2b8fcc2d 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -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 diff --git a/drivers/soc/qcom/msm-spm.c b/drivers/soc/qcom/msm-spm.c new file mode 100644 index 000000000000..855e8763b417 --- /dev/null +++ b/drivers/soc/qcom/msm-spm.c @@ -0,0 +1,762 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2017, 2020-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/drivers/soc/qcom/spm_devices.c b/drivers/soc/qcom/spm_devices.c new file mode 100644 index 000000000000..3640c7f21ab3 --- /dev/null +++ b/drivers/soc/qcom/spm_devices.c @@ -0,0 +1,1002 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spm_driver.h" + +#define VDD_DEFAULT 0xDEADF00D +#define SLP_CMD_BIT 17 +#define PC_MODE_BIT 16 +#define RET_MODE_BIT 15 +#define EVENT_SYNC_BIT 24 +#define ISAR_BIT 3 +#define SPM_EN_BIT 0 + +struct msm_spm_power_modes { + uint32_t mode; + uint32_t ctl; +}; + +struct msm_spm_device { + struct list_head list; + bool initialized; + const char *name; + struct msm_spm_driver_data reg_data; + struct msm_spm_power_modes *modes; + uint32_t num_modes; + uint32_t cpu_vdd; + struct cpumask mask; + void __iomem *q2s_reg; + bool qchannel_ignore; + bool allow_rpm_hs; + bool use_spm_clk_gating; + bool use_qchannel_for_wfi; + void __iomem *flush_base_addr; + void __iomem *slpreq_base_addr; +}; + +struct msm_spm_vdd_info { + struct msm_spm_device *vctl_dev; + uint32_t vlevel; + int err; +}; + +static LIST_HEAD(spm_list); +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device); +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device); + +static void msm_spm_smp_set_vdd(void *data) +{ + struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data; + struct msm_spm_device *dev = info->vctl_dev; + + dev->cpu_vdd = info->vlevel; + info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel); +} + +/** + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2 + * probe. + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER. + * if probe failed, then return the err number for that failure. + */ +int msm_spm_probe_done(void) +{ + struct msm_spm_device *dev; + int cpu; + int ret = 0; + + for_each_possible_cpu(cpu) { + dev = per_cpu(cpu_vctl_device, cpu); + if (!dev) + return -EPROBE_DEFER; + + ret = IS_ERR(dev); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(msm_spm_probe_done); + +void msm_spm_dump_regs(unsigned int cpu) +{ + dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu); +} + +/** + * msm_spm_set_vdd(): Set core voltage + * @cpu: core id + * @vlevel: Encoded PMIC data. + * + * Return: 0 on success or -(ERRNO) on failure. + */ +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) +{ + struct msm_spm_vdd_info info; + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + int ret; + + if (!dev) + return -EPROBE_DEFER; + + ret = IS_ERR(dev); + if (ret) + return ret; + + info.vctl_dev = dev; + info.vlevel = vlevel; + + ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info, + true); + if (ret) + return ret; + + return info.err; +} +EXPORT_SYMBOL(msm_spm_set_vdd); + +/** + * msm_spm_get_vdd(): Get core voltage + * @cpu: core id + * @return: Returns encoded PMIC data. + */ +int msm_spm_get_vdd(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -EPROBE_DEFER; + + return msm_spm_drv_get_vdd(&dev->reg_data) ? : -EINVAL; +} +EXPORT_SYMBOL(msm_spm_get_vdd); + +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode) +{ + uint32_t spm_legacy_mode = 0; + uint32_t qchannel_ignore = 0; + uint32_t val = 0; + + if (!dev->q2s_reg) + return; + + switch (mode) { + case MSM_SPM_MODE_DISABLED: + case MSM_SPM_MODE_CLOCK_GATING: + qchannel_ignore = !dev->use_qchannel_for_wfi; + spm_legacy_mode = 0; + break; + case MSM_SPM_MODE_RETENTION: + qchannel_ignore = 0; + spm_legacy_mode = 0; + break; + case MSM_SPM_MODE_GDHS: + case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE: + case MSM_SPM_MODE_POWER_COLLAPSE: + qchannel_ignore = dev->qchannel_ignore; + spm_legacy_mode = 1; + break; + default: + break; + } + + val = spm_legacy_mode << 2 | qchannel_ignore << 1; + __raw_writel(val, dev->q2s_reg); + mb(); /* Ensure flush */ +} + +static void msm_spm_config_hw_flush(struct msm_spm_device *dev, + unsigned int mode) +{ + uint32_t val = 0; + + if (!dev->flush_base_addr) + return; + + switch (mode) { + case MSM_SPM_MODE_FASTPC: + case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE: + case MSM_SPM_MODE_POWER_COLLAPSE: + val = BIT(0); + break; + default: + break; + } + + __raw_writel(val, dev->flush_base_addr); +} + +static void msm_spm_config_slpreq(struct msm_spm_device *dev, + unsigned int mode) +{ + uint32_t val = 0; + + if (!dev->slpreq_base_addr) + return; + + switch (mode) { + case MSM_SPM_MODE_FASTPC: + case MSM_SPM_MODE_GDHS: + case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE: + case MSM_SPM_MODE_POWER_COLLAPSE: + val = BIT(4); + break; + default: + break; + } + + val = (__raw_readl(dev->slpreq_base_addr) & ~BIT(4)) | val; + __raw_writel(val, dev->slpreq_base_addr); +} + +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm, bool set_spm_enable) +{ + uint32_t i; + int ret = -EINVAL; + uint32_t ctl = 0; + + if (!dev) { + pr_err("dev is NULL\n"); + return -ENODEV; + } + + if (!dev->initialized) + return -ENXIO; + + if (!dev->num_modes) + return 0; + + if (mode == MSM_SPM_MODE_DISABLED && set_spm_enable) { + ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false); + } else { + if (set_spm_enable) + ret = msm_spm_drv_set_spm_enable(&dev->reg_data, true); + for (i = 0; i < dev->num_modes; i++) { + if (dev->modes[i].mode != mode) + continue; + + ctl = dev->modes[i].ctl; + if (!dev->allow_rpm_hs && notify_rpm) + ctl &= ~BIT(SLP_CMD_BIT); + + break; + } + ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, ctl); + } + + msm_spm_config_q2s(dev, mode); + msm_spm_config_hw_flush(dev, mode); + msm_spm_config_slpreq(dev, mode); + + return ret; +} + +static int msm_spm_dev_init(struct msm_spm_device *dev, + struct msm_spm_platform_data *data) +{ + int i, ret = -ENOMEM; + uint32_t offset = 0; + + dev->cpu_vdd = VDD_DEFAULT; + dev->num_modes = data->num_modes; + dev->modes = kmalloc_array( + dev->num_modes, sizeof(struct msm_spm_power_modes), + GFP_KERNEL); + + if (!dev->modes) + goto spm_failed_malloc; + + ret = msm_spm_drv_init(&dev->reg_data, data); + + if (ret) + goto spm_failed_init; + + for (i = 0; i < dev->num_modes; i++) { + + /* Default offset is 0 and gets updated as we write more + * sequences into SPM + */ + dev->modes[i].ctl = data->modes[i].ctl | ((offset & 0x1FF) + << 4); + ret = msm_spm_drv_write_seq_data(&dev->reg_data, + data->modes[i].cmd, &offset); + if (ret < 0) + goto spm_failed_init; + + dev->modes[i].mode = data->modes[i].mode; + } + + msm_spm_drv_reinit(&dev->reg_data, dev->num_modes ? true : false); + + dev->initialized = true; + + return 0; + +spm_failed_init: + kfree(dev->modes); +spm_failed_malloc: + return ret; +} + +/** + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core + * @node: The SPM node that controls the voltage for the CPU + * @val: The value to be set on the rail + * @cpu: The cpu for this with rail is being powered on + */ +int msm_spm_turn_on_cpu_rail(struct device_node *vctl_node, + unsigned int val, int cpu, int vctl_offset) +{ + uint32_t timeout = 2000; /* delay for voltage to settle on the core */ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + void __iomem *base; + + base = of_iomap(vctl_node, 1); + if (base) { + /* + * Program Q2S to disable SPM legacy mode and ignore Q2S + * channel requests. + * bit[1] = qchannel_ignore = 1 + * bit[2] = spm_legacy_mode = 0 + */ + writel_relaxed(0x2, base); + mb(); /* Ensure flush */ + iounmap(base); + } + + base = of_iomap(vctl_node, 0); + if (!base) + return -ENOMEM; + + if (dev && (dev->cpu_vdd != VDD_DEFAULT)) + return 0; + + /* Set the CPU supply regulator voltage */ + val = (val & 0xFF); + writel_relaxed(val, base + vctl_offset); + mb(); /* Ensure flush */ + udelay(timeout); + + /* Enable the CPU supply regulator*/ + val = 0x30080; + writel_relaxed(val, base + vctl_offset); + mb(); /* Ensure flush */ + udelay(timeout); + + iounmap(base); + + return 0; +} +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail); + +void msm_spm_reinit(void) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) + msm_spm_drv_reinit( + &per_cpu(msm_cpu_spm_device.reg_data, cpu), true); +} +EXPORT_SYMBOL(msm_spm_reinit); + +/* + * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu + * It should only be used to decide a mode before lpm driver is probed. + * @mode: SPM LPM mode to be selected + */ +bool msm_spm_is_mode_avail(unsigned int mode) +{ + struct msm_spm_device *dev = this_cpu_ptr(&msm_cpu_spm_device); + int i; + + for (i = 0; i < dev->num_modes; i++) { + if (dev->modes[i].mode == mode) + return true; + } + + return false; +} + +/** + * msm_spm_is_avs_enabled() - Functions returns 1 if AVS is enabled and + * 0 if it is not. + * @cpu: specifies cpu's avs should be read + * + * Returns errno in case of failure or AVS enable state otherwise + */ +int msm_spm_is_avs_enabled(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_get_avs_enable(&dev->reg_data); +} +EXPORT_SYMBOL(msm_spm_is_avs_enabled); + +/** + * msm_spm_avs_enable() - Enables AVS on the SAW that controls this cpu's + * voltage. + * @cpu: specifies which cpu's avs should be enabled + * + * Returns errno in case of failure or 0 if successful + */ +int msm_spm_avs_enable(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_enable(&dev->reg_data, true); +} +EXPORT_SYMBOL(msm_spm_avs_enable); + +/** + * msm_spm_avs_disable() - Disables AVS on the SAW that controls this cpu's + * voltage. + * @cpu: specifies which cpu's avs should be enabled + * + * Returns errno in case of failure or 0 if successful + */ +int msm_spm_avs_disable(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_enable(&dev->reg_data, false); +} +EXPORT_SYMBOL(msm_spm_avs_disable); + +/** + * msm_spm_avs_set_limit() - Set maximum and minimum AVS limits on the + * SAW that controls this cpu's voltage. + * @cpu: specify which cpu's avs should be configured + * @min_lvl: specifies the minimum PMIC output voltage control register + * value that may be sent to the PMIC + * @max_lvl: specifies the maximum PMIC output voltage control register + * value that may be sent to the PMIC + * Returns errno in case of failure or 0 if successful + */ +int msm_spm_avs_set_limit(unsigned int cpu, + uint32_t min_lvl, uint32_t max_lvl) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_limit(&dev->reg_data, min_lvl, max_lvl); +} +EXPORT_SYMBOL(msm_spm_avs_set_limit); + +/** + * msm_spm_avs_enable_irq() - Enable an AVS interrupt + * @cpu: specifies which CPU's AVS should be configured + * @irq: specifies which interrupt to enable + * + * Returns errno in case of failure or 0 if successful. + */ +int msm_spm_avs_enable_irq(unsigned int cpu, enum msm_spm_avs_irq irq) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_irq_enable(&dev->reg_data, irq, true); +} +EXPORT_SYMBOL(msm_spm_avs_enable_irq); + +/** + * msm_spm_avs_disable_irq() - Disable an AVS interrupt + * @cpu: specifies which CPU's AVS should be configured + * @irq: specifies which interrupt to disable + * + * Returns errno in case of failure or 0 if successful. + */ +int msm_spm_avs_disable_irq(unsigned int cpu, enum msm_spm_avs_irq irq) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_irq_enable(&dev->reg_data, irq, false); +} +EXPORT_SYMBOL(msm_spm_avs_disable_irq); + +/** + * msm_spm_avs_clear_irq() - Clear a latched AVS interrupt + * @cpu: specifies which CPU's AVS should be configured + * @irq: specifies which interrupt to clear + * + * Returns errno in case of failure or 0 if successful. + */ +int msm_spm_avs_clear_irq(unsigned int cpu, enum msm_spm_avs_irq irq) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_avs_clear_irq(&dev->reg_data, irq); +} +EXPORT_SYMBOL(msm_spm_avs_clear_irq); + +/** + * 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 = this_cpu_ptr(&msm_cpu_spm_device); + + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm, true); +} +EXPORT_SYMBOL(msm_spm_set_low_power_mode); + +void msm_spm_set_rpm_hs(bool allow_rpm_hs) +{ + struct msm_spm_device *dev = this_cpu_ptr(&msm_cpu_spm_device); + + dev->allow_rpm_hs = allow_rpm_hs; +} +EXPORT_SYMBOL(msm_spm_set_rpm_hs); + +int msm_spm_config_low_power_mode_addr(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm) +{ + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm, false); +} + +/** + * 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; + int ret = 0; + + if ((nr_devs < num_possible_cpus()) || !data) + return -EINVAL; + + for_each_possible_cpu(cpu) { + struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu); + + ret = msm_spm_dev_init(dev, &data[cpu]); + if (ret < 0) { + pr_warn("%s():failed CPU:%u ret:%d\n", __func__, + cpu, ret); + break; + } + } + + return ret; +} + +struct msm_spm_device *msm_spm_get_device_by_name(const char *name) +{ + struct list_head *list; + + list_for_each(list, &spm_list) { + struct msm_spm_device *dev + = list_entry(list, typeof(*dev), list); + if (dev->name && !strcmp(dev->name, name)) + return dev; + } + return ERR_PTR(-ENODEV); +} + +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm) +{ + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm, true); +} +#ifdef CONFIG_MSM_L2_SPM + +/** + * msm_spm_apcs_set_phase(): Set number of SMPS phases. + * @cpu: cpu which is requesting the change in number of phases. + * @phase_cnt: Number of phases to be set active + */ +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_pmic_data(&dev->reg_data, + MSM_SPM_PMIC_PHASE_PORT, phase_cnt); +} +EXPORT_SYMBOL(msm_spm_apcs_set_phase); + +/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power + * when the cores are in low power modes + * @cpu: cpu that is entering low power mode. + * @mode: The mode configuration for FTS + */ +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_pmic_data(&dev->reg_data, + MSM_SPM_PMIC_PFM_PORT, mode); +} +EXPORT_SYMBOL(msm_spm_enable_fts_lpm); + +#endif + +static int get_cpu_id(struct device_node *node) +{ + struct device_node *cpu_node; + u32 cpu; + char *key = "qcom,cpu"; + + cpu_node = of_parse_phandle(node, key, 0); + if (cpu_node) { + for_each_possible_cpu(cpu) { + if (of_get_cpu_node(cpu, NULL) == cpu_node) + return cpu; + } + } else + return num_possible_cpus(); + + return -EINVAL; +} + +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev) +{ + struct msm_spm_device *dev = NULL; + const char *val = NULL; + char *key = "qcom,name"; + int cpu = get_cpu_id(pdev->dev.of_node); + + if ((cpu >= 0) && cpu < num_possible_cpus()) + dev = &per_cpu(msm_cpu_spm_device, cpu); + else if (cpu == num_possible_cpus()) + dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device), + GFP_KERNEL); + + if (!dev) + return NULL; + + if (of_property_read_string(pdev->dev.of_node, key, &val)) { + pr_err("%s(): Cannot find a required node key:%s\n", + __func__, key); + return NULL; + } + dev->name = val; + list_add(&dev->list, &spm_list); + + return dev; +} + +static void get_cpumask(struct device_node *node, struct cpumask *mask) +{ + unsigned int c; + int idx = 0; + struct device_node *cpu_node; + char *key = "qcom,cpu-vctl-list"; + + cpu_node = of_parse_phandle(node, key, idx++); + while (cpu_node) { + for_each_possible_cpu(c) { + if (of_get_cpu_node(c, NULL) == cpu_node) + cpumask_set_cpu(c, mask); + } + cpu_node = of_parse_phandle(node, key, idx++); + } +} + +static int msm_spm_dev_probe(struct platform_device *pdev) +{ + int ret = 0; + int cpu = 0; + int i = 0; + struct device_node *node = pdev->dev.of_node; + struct device_node *n = NULL; + struct msm_spm_platform_data spm_data; + char *key = NULL; + uint32_t val = 0; + struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR]; + int len = 0; + struct msm_spm_device *dev = NULL; + struct resource *res = NULL; + uint32_t mode_count = 0; + + struct spm_of { + char *key; + uint32_t id; + }; + + struct spm_of spm_of_data[] = { + {"qcom,saw2-cfg", MSM_SPM_REG_SAW_CFG}, + {"qcom,saw2-avs-ctl", MSM_SPM_REG_SAW_AVS_CTL}, + {"qcom,saw2-avs-hysteresis", MSM_SPM_REG_SAW_AVS_HYSTERESIS}, + {"qcom,saw2-avs-limit", MSM_SPM_REG_SAW_AVS_LIMIT}, + {"qcom,saw2-avs-dly", MSM_SPM_REG_SAW_AVS_DLY}, + {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW_SPM_DLY}, + {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW_SPM_CTL}, + {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW_PMIC_DATA_0}, + {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW_PMIC_DATA_1}, + {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW_PMIC_DATA_2}, + {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW_PMIC_DATA_3}, + {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW_PMIC_DATA_4}, + {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW_PMIC_DATA_5}, + {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW_PMIC_DATA_6}, + {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW_PMIC_DATA_7}, + }; + + struct mode_of { + char *key; + uint32_t id; + }; + + struct mode_of mode_of_data[] = { + {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING}, + {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION}, + {"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS}, + {"qcom,saw2-spm-cmd-spc", + MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE}, + {"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE}, + {"qcom,saw2-spm-cmd-fpc", MSM_SPM_MODE_FASTPC}, + }; + + dev = msm_spm_get_device(pdev); + if (!dev) { + /* + * For partial goods support some CPUs might not be available + * in which case, shouldn't throw an error + */ + return 0; + } + get_cpumask(node, &dev->mask); + + memset(&spm_data, 0, sizeof(struct msm_spm_platform_data)); + memset(&modes, 0, + (MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry)); + + key = "qcom,saw2-ver-reg"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + spm_data.ver_reg = val; + + key = "qcom,vctl-timeout-us"; + ret = of_property_read_u32(node, key, &val); + if (!ret) + spm_data.vctl_timeout_us = val; + + /* SAW start address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EFAULT; + goto fail; + } + + spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!spm_data.reg_base_addr) { + ret = -ENOMEM; + goto fail; + } + + spm_data.vctl_port = -1; + spm_data.vctl_port_ub = -1; + spm_data.phase_port = -1; + spm_data.pfm_port = -1; + + key = "qcom,vctl-port"; + of_property_read_u32(node, key, &spm_data.vctl_port); + + key = "qcom,vctl-port-ub"; + of_property_read_u32(node, key, &spm_data.vctl_port_ub); + + key = "qcom,phase-port"; + of_property_read_u32(node, key, &spm_data.phase_port); + + key = "qcom,pfm-port"; + of_property_read_u32(node, key, &spm_data.pfm_port); + + /* Q2S (QChannel-2-SPM) register */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "q2s"); + if (res) { + dev->q2s_reg = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!dev->q2s_reg) { + pr_err("%s(): Unable to iomap Q2S register\n", + __func__); + ret = -EADDRNOTAVAIL; + goto fail; + } + } + + key = "qcom,use-qchannel-for-pc"; + dev->qchannel_ignore = !of_property_read_bool(node, key); + + key = "qcom,use-spm-clock-gating"; + dev->use_spm_clk_gating = of_property_read_bool(node, key); + + key = "qcom,use-qchannel-for-wfi"; + dev->use_qchannel_for_wfi = of_property_read_bool(node, key); + + /* HW flush address */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hw-flush"); + if (res) { + dev->flush_base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->flush_base_addr)) { + ret = PTR_ERR(dev->flush_base_addr); + pr_err("%s(): Unable to iomap hw flush register %d\n", + __func__, ret); + goto fail; + } + } + + /* Sleep req address */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "slpreq"); + if (res) { + dev->slpreq_base_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!dev->slpreq_base_addr) { + ret = -ENOMEM; + pr_err("%s(): Unable to iomap slpreq register\n", + __func__); + ret = -EADDRNOTAVAIL; + goto fail; + } + } + + /* + * At system boot, cpus and or clusters can remain in reset. CCI SPM + * will not be triggered unless SPM_LEGACY_MODE bit is set for the + * cluster in reset. Initialize q2s registers and set the + * SPM_LEGACY_MODE bit. + */ + msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE); + msm_spm_drv_reg_init(&dev->reg_data, &spm_data); + + for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) { + ret = of_property_read_u32(node, spm_of_data[i].key, &val); + if (ret) + continue; + msm_spm_drv_upd_reg_shadow(&dev->reg_data, spm_of_data[i].id, + val); + } + + for_each_child_of_node(node, n) { + const char *name; + bool bit_set; + int sync; + + if (!n->name) + continue; + + ret = of_property_read_string(n, "qcom,label", &name); + if (ret) + continue; + + for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) + if (!strcmp(name, mode_of_data[i].key)) + break; + + if (i == ARRAY_SIZE(mode_of_data)) { + pr_err("Mode name invalid %s\n", name); + break; + } + + modes[mode_count].mode = mode_of_data[i].id; + modes[mode_count].cmd = + (uint8_t *)of_get_property(n, "qcom,sequence", &len); + if (!modes[mode_count].cmd) { + pr_err("cmd is empty\n"); + continue; + } + + bit_set = of_property_read_bool(n, "qcom,pc_mode"); + modes[mode_count].ctl |= bit_set ? BIT(PC_MODE_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,ret_mode"); + modes[mode_count].ctl |= bit_set ? BIT(RET_MODE_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,slp_cmd_mode"); + modes[mode_count].ctl |= bit_set ? BIT(SLP_CMD_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,isar"); + modes[mode_count].ctl |= bit_set ? BIT(ISAR_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,spm_en"); + modes[mode_count].ctl |= bit_set ? BIT(SPM_EN_BIT) : 0; + + ret = of_property_read_u32(n, "qcom,event_sync", &sync); + if (!ret) + modes[mode_count].ctl |= sync << EVENT_SYNC_BIT; + + mode_count++; + } + + spm_data.modes = modes; + spm_data.num_modes = mode_count; + + key = "qcom,supports-rpm-hs"; + dev->allow_rpm_hs = of_property_read_bool(pdev->dev.of_node, key); + + ret = msm_spm_dev_init(dev, &spm_data); + if (ret) + pr_err("SPM modes programming is not available from HLOS\n"); + + platform_set_drvdata(pdev, dev); + + for_each_cpu(cpu, &dev->mask) + per_cpu(cpu_vctl_device, cpu) = dev; + + if (!spm_data.num_modes) + return 0; + + cpu = get_cpu_id(pdev->dev.of_node); + + /* For CPUs that are online, the SPM has to be programmed for + * clockgating mode to ensure that it can use SPM for entering these + * low power modes. + */ + get_online_cpus(); + if ((cpu >= 0) && (cpu < num_possible_cpus()) && (cpu_online(cpu))) + msm_spm_config_low_power_mode(dev, MSM_SPM_MODE_CLOCK_GATING, + false); + put_online_cpus(); + return ret; + +fail: + cpu = get_cpu_id(pdev->dev.of_node); + if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) { + for_each_cpu(cpu, &dev->mask) + per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret); + } + + pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret); + + return ret; +} + +static int msm_spm_dev_remove(struct platform_device *pdev) +{ + struct msm_spm_device *dev = platform_get_drvdata(pdev); + + list_del(&dev->list); + return 0; +} + +static const struct of_device_id msm_spm_match_table[] = { + {.compatible = "qcom,spm-v2"}, + {}, +}; + +static struct platform_driver msm_spm_device_driver = { + .probe = msm_spm_dev_probe, + .remove = msm_spm_dev_remove, + .driver = { + .name = "spm-v2", + .of_match_table = msm_spm_match_table, + }, +}; + +/** + * msm_spm_device_init(): Device tree initialization function + */ +int __init msm_spm_device_init(void) +{ + static bool registered; + + if (registered) + return 0; + registered = true; + return platform_driver_register(&msm_spm_device_driver); +} +arch_initcall(msm_spm_device_init); diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h new file mode 100644 index 000000000000..477ba0b999ef --- /dev/null +++ b/drivers/soc/qcom/spm_driver.h @@ -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 + +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 diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h new file mode 100644 index 000000000000..71d1510b2719 --- /dev/null +++ b/include/soc/qcom/spm.h @@ -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 */ From 92d0c40a5c72ca468e4affbe2cd241cb394cac95 Mon Sep 17 00:00:00 2001 From: Raja Mallik Date: Fri, 13 Dec 2019 11:23:21 +0530 Subject: [PATCH 3/3] drivers: soc: qcom: pm: Add legacy PM and Warmboot set address API support Add legacy msm pm sleep mode support for MDM9607 target. Add warmboot set address support for multi cluster cpu's. Snapshot is taken from msm-4.9 kernel version @ commit 078db469522464 ("soc: qcom: bgrsb: Increase time out for RSB channel opening"). Change-Id: Ibfc56254c47cfc1465aff5559b8846072f66041a Signed-off-by: Raghavendra Kakarla Signed-off-by: Haribabu Gattem Signed-off-by: Raja Mallik Signed-off-by: Tushar Nimkar --- drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/idle.h | 21 + drivers/soc/qcom/msm-pm.c | 819 +++++++++++++++++++++++++++++++++++++ drivers/soc/qcom/pm-boot.c | 71 ++++ drivers/soc/qcom/pm-boot.h | 13 + 5 files changed, 925 insertions(+) create mode 100644 drivers/soc/qcom/idle.h create mode 100644 drivers/soc/qcom/msm-pm.c create mode 100644 drivers/soc/qcom/pm-boot.c create mode 100644 drivers/soc/qcom/pm-boot.h diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 177a2b8fcc2d..0cc6fa88a236 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -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_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 diff --git a/drivers/soc/qcom/idle.h b/drivers/soc/qcom/idle.h new file mode 100644 index 000000000000..bf67a4221729 --- /dev/null +++ b/drivers/soc/qcom/idle.h @@ -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 diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c new file mode 100644 index 000000000000..9209eb214740 --- /dev/null +++ b/drivers/soc/qcom/msm-pm.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_VFP +#include +#endif +#include +#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 diff --git a/drivers/soc/qcom/pm-boot.c b/drivers/soc/qcom/pm-boot.c new file mode 100644 index 000000000000..7ad6c3049202 --- /dev/null +++ b/drivers/soc/qcom/pm-boot.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2014, 2016, 2018-2019, 2021 The Linux Foundation. + */ + +#include +#include +#include +#include +#include +#include +#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); diff --git a/drivers/soc/qcom/pm-boot.h b/drivers/soc/qcom/pm-boot.h new file mode 100644 index 000000000000..fd1b638a940c --- /dev/null +++ b/drivers/soc/qcom/pm-boot.h @@ -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