new file mode 100644
@@ -0,0 +1,104 @@
+* MSM Subsystem Power Manager (spm-v2)
+
+S4 generation of MSMs have SPM hardware blocks to control the Application
+Processor Sub-System power. These SPM blocks run individual state machine
+to determine what the core (L2 or Krait/Scorpion) would do when the WFI
+instruction is executed by the core. The SAW hardware block handles SPM and
+AVS functionality for the cores.
+
+The devicetree representation of the SPM block should be:
+
+Required properties
+
+- compatible: "qcom,spm-v2"
+- reg: The physical address and the size of the SPM's memory mapped registers
+- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets
+ that dont support CPU phandles the driver would support qcom,core-id.
+ This field is required on only for SPMs that control the CPU.
+- qcom, core-id: This property will be deprecated once all targets start
+ supporting CPU phandles. This field will be used to identify SPMs
+ that control the CPU.
+ {0..n} for cores {0..n}
+- qcom,saw2-ver-reg: The location of the version register
+- qcom,saw2-cfg: SAW2 configuration register
+- qcom,saw2-avs-ctl: The AVS control register
+- qcom,saw2-avs-hysterisis: The AVS hysterisis register to delay the AVS
+ controller requests
+- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
+ sequence
+- qcom,saw2-spm-ctl: The SPM control register
+- qcom,vctl-timeout-us: The timeout value in us to wait for voltage to change
+ after sending the voltage command to the PMIC
+- qcom,name: The name with which a SPM device is identified by the power
+management code.
+
+Optional properties
+
+- qcom,saw2-avs-limit: The AVS limit register
+- qcom,saw2-avs-dly: The AVS delay register is used to specify the delay values
+ between AVS controller requests
+- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
+ index to send the PMIC data to
+- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
+ voltage
+- qcom,phase-port: The PVC port used for changing the number of phases
+- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
+- qcom,saw2-spm-cmd-wfi: The WFI command sequence
+- qcom,saw2-spm-cmd-ret: The Retention command sequence
+- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
+- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS
+ proc won't inform the RPM.
+- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence
+- qcom,saw2-spm-cmd-gdhs: L2 GDHS command sequence
+- qcom,cpu-vctl-mask: Mask of cpus, whose voltage the spm device can control.
+ Depricated: Replaced with cpu-vctl-list when cpu phandles are available.
+- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
+ can control.
+
+Example 1:
+ qcom,spm@f9089000 {
+ compatible = "qcom,spm-v2";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xf9089000 0x1000>;
+ qcom,core-id = <0>;
+ qcom,saw2-ver-reg = <0xfd0>;
+ qcom,saw2-cfg = <0x1b>;
+ qcom,saw2-avs-ctl = <0>;
+ qcom,saw2-avs-hysteresis = <0>;
+ qcom,saw2-avs-limit = <0>;
+ qcom,saw2-avs-dly= <0>;
+ qcom,saw2-spm-dly= <0x20000400>;
+ qcom,saw2-spm-ctl = <0x1>;
+ qcom,cpu-vctl-mask = <0xf>;
+ qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+ qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
+ a0 b0 03 68 70 3b 92 a0 b0
+ 82 2b 50 10 30 02 22 30 0f];
+ qcom,saw2-spm-cmd-pc = [00 20 10 92 a0 b0 07 3b 92
+ a0 b0 82 10 30 02 22 30 0f];
+ };
+
+Example 2:
+ qcom,spm@f9089000 {
+ compatible = "qcom,spm-v2";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xf9089000 0x1000>;
+ qcom,core-id = <0>;
+ qcom,saw2-ver-reg = <0xfd0>;
+ qcom,saw2-cfg = <0x1b>;
+ qcom,saw2-avs-ctl = <0>;
+ qcom,saw2-avs-hysteresis = <0>;
+ qcom,saw2-avs-limit = <0>;
+ qcom,saw2-avs-dly= <0>;
+ qcom,saw2-spm-dly= <0x20000400>;
+ qcom,saw2-spm-ctl = <0x1>;
+ qcom,cpu-vctl-list = <&CPU0 &CPU1 &CPU2 &CPU3>;
+ qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+ qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
+ a0 b0 03 68 70 3b 92 a0 b0
+ 82 2b 50 10 30 02 22 30 0f];
+ qcom,saw2-spm-cmd-pc = [00 20 10 92 a0 b0 07 3b 92
+ a0 b0 82 10 30 02 22 30 0f];
+ };
@@ -1,3 +1,5 @@
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
+obj-$(CONFIG_QCOM_PM) += spm_devices.o spm.o
+
CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
new file mode 100644
@@ -0,0 +1,559 @@
+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <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, S_IRUGO | S_IWUSR | S_IWGRP
+);
+
+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_SAW2_SECURE] = 0x00,
+ [MSM_SPM_REG_SAW2_ID] = 0x04,
+ [MSM_SPM_REG_SAW2_CFG] = 0x08,
+ [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C,
+ [MSM_SPM_REG_SAW2_AVS_STS] = 0x10,
+ [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14,
+ [MSM_SPM_REG_SAW2_RST] = 0x18,
+ [MSM_SPM_REG_SAW2_VCTL] = 0x1C,
+ [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20,
+ [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24,
+ [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28,
+ [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C,
+ [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30,
+ [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C,
+ [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80,
+ [MSM_SPM_REG_SAW2_VERSION] = 0xFD0,
+};
+
+static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
+ [MSM_SPM_REG_SAW2_SECURE] = 0x00,
+ [MSM_SPM_REG_SAW2_ID] = 0x04,
+ [MSM_SPM_REG_SAW2_CFG] = 0x08,
+ [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C,
+ [MSM_SPM_REG_SAW2_AVS_STS] = 0x10,
+ [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14,
+ [MSM_SPM_REG_SAW2_RST] = 0x18,
+ [MSM_SPM_REG_SAW2_VCTL] = 0x1C,
+ [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20,
+ [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24,
+ [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28,
+ [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C,
+ [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30,
+ [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34,
+ [MSM_SPM_REG_SAW2_STS2] = 0x38,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C,
+ [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x400,
+ [MSM_SPM_REG_SAW2_VERSION] = 0xFD0,
+};
+
+static struct saw2_data saw2_info[] = {
+ [0] = {
+ "SAW2_v2.1",
+ 0x2,
+ 0x1,
+ msm_spm_reg_offsets_saw2_v2_1,
+ },
+ [1] = {
+ "SAW2_v3.0",
+ 0x3,
+ 0x0,
+ msm_spm_reg_offsets_saw2_v3_0,
+ },
+};
+
+static uint32_t num_pmic_data;
+
+static inline uint32_t msm_spm_drv_get_num_spm_entry(
+ struct msm_spm_driver_data *dev)
+{
+ return 32;
+}
+
+static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
+ unsigned int reg_index)
+{
+ __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 void msm_spm_drv_set_start_addr(
+ struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode)
+{
+ addr &= 0x7F;
+ addr <<= 4;
+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
+
+ if (dev->major != 0x3)
+ return;
+
+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF;
+ if (pc_mode)
+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000;
+}
+
+static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
+{
+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID);
+ return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1;
+}
+
+static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
+ uint32_t vlevel)
+{
+ unsigned int pmic_data = 0;
+
+ /**
+ * VCTL_PORT has to be 0, for PMIC_STS register to be updated.
+ * Ensure that vctl_port is always set to 0.
+ */
+ WARN_ON(dev->vctl_port);
+
+ pmic_data |= vlevel;
+ pmic_data |= (dev->vctl_port & 0x7) << 16;
+
+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF;
+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
+
+ dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF;
+ dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data;
+
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_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_SAW2_ID);
+ mb();
+ return (dev->reg_shadow[MSM_SPM_REG_SAW2_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_SAW2_PMIC_STS);
+ return (dev->reg_shadow[MSM_SPM_REG_SAW2_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_SAW2_PMIC_STS);
+ return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF;
+}
+
+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_SAW2_VERSION] =
+ __raw_readl(dev->reg_base_addr + dev->ver_reg);
+
+ val = dev->reg_shadow[MSM_SPM_REG_SAW2_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_SAW2_SPM_CTL] & 0x01) ^ value) {
+
+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
+
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
+ wmb();
+ }
+ 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_SAW2_SEQ_ENTRY]
+ + 4 * i);
+ }
+ mb();
+}
+
+void dump_regs(struct msm_spm_driver_data *dev, int cpu)
+{
+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS);
+ mb();
+ pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu,
+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]);
+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
+ mb();
+ pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu,
+ dev->reg_shadow[MSM_SPM_REG_SAW2_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 addr, bool pc_mode)
+{
+
+ /* SPM is configured to reset start address to zero after end of Program
+ */
+ if (!dev)
+ return -EINVAL;
+
+ msm_spm_drv_set_start_addr(dev, addr, pc_mode);
+
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
+ wmb();
+
+ 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_SAW2_SPM_STS);
+
+ return 0;
+}
+
+#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_SAW2_AVS_CTL);
+ return dev->reg_shadow[MSM_SPM_REG_SAW2_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_SAW2_AVS_CTL);
+ dev->reg_shadow[MSM_SPM_REG_SAW2_AVS_CTL] &= ~BIT(27);
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_AVS_CTL);
+}
+
+static void msm_spm_drv_enable_avs(struct msm_spm_driver_data *dev)
+{
+ dev->reg_shadow[MSM_SPM_REG_SAW2_AVS_CTL] |= BIT(27);
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_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_SAW2_AVS_CTL] &= ~0x7efc00;
+ dev->reg_shadow[MSM_SPM_REG_SAW2_AVS_CTL] |= ((vlevel - 4) << 10);
+ dev->reg_shadow[MSM_SPM_REG_SAW2_AVS_CTL] |= (vlevel << 17);
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_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
+
+int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
+{
+ uint32_t timeout_us, new_level;
+ bool avs_enabled;
+
+ if (!dev)
+ return -EINVAL;
+
+ avs_enabled = msm_spm_drv_is_avs_enabled(dev);
+
+ if (!msm_spm_pmic_arb_present(dev))
+ return -ENOSYS;
+
+ 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);
+
+ /* Kick the state machine back to idle */
+ dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1;
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST);
+
+ msm_spm_drv_set_vctl2(dev, vlevel);
+
+ timeout_us = dev->vctl_timeout_us;
+ /* Confirm the voltage we set was what hardware sent */
+ do {
+ new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
+ if (new_level == vlevel)
+ break;
+ udelay(1);
+ } while (--timeout_us);
+ if (!timeout_us) {
+ pr_info("Wrong level %#x\n", new_level);
+ goto set_vdd_bail;
+ }
+
+ if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
+ pr_info("%s: done, remaining timeout %u us\n",
+ __func__, timeout_us);
+
+ /* Set AVS min/max */
+ if (avs_enabled) {
+ msm_spm_drv_set_avs_vlevel(dev, vlevel);
+ msm_spm_drv_enable_avs(dev);
+ }
+
+ return 0;
+
+set_vdd_bail:
+ if (avs_enabled)
+ msm_spm_drv_enable_avs(dev);
+
+ pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n",
+ __func__, vlevel, timeout_us, new_level);
+ 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 -ENOSYS;
+
+ 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_SAW2_VCTL] &= ~0x700FF;
+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data;
+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
+ mb();
+
+ 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)
+{
+ int i;
+
+ msm_spm_drv_flush_seq_entry(dev);
+ for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++)
+ msm_spm_drv_flush_shadow(dev, i);
+
+ mb();
+
+ 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_init(struct msm_spm_driver_data *dev,
+ struct msm_spm_platform_data *data)
+{
+ int i;
+ int num_spm_entry;
+ bool found = false;
+
+ BUG_ON(!dev || !data);
+
+ dev->vctl_port = data->vctl_port;
+ 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;
+
+ 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 SAW2 version found\n", __func__);
+ BUG_ON(!found);
+ }
+
+ 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 =
+ kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
+ GFP_KERNEL);
+
+ if (!dev->reg_seq_entry_shadow)
+ return -ENOMEM;
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,709 @@
+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+
+#include <soc/qcom/spm.h>
+
+#include "spm_driver.h"
+
+#define VDD_DEFAULT 0xDEADF00D
+
+struct msm_spm_power_modes {
+ uint32_t mode;
+ bool notify_rpm;
+ uint32_t start_addr;
+};
+
+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;
+};
+
+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.
+ */
+unsigned int msm_spm_get_vdd(unsigned int cpu)
+{
+ int ret;
+ struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
+
+ if (!dev)
+ return -EPROBE_DEFER;
+
+ ret = IS_ERR(dev);
+ if (ret)
+ return ret;
+
+ return dev->cpu_vdd;
+}
+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 = 1;
+ 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_POWER_COLLAPSE:
+ qchannel_ignore = 0;
+ spm_legacy_mode = 1;
+ break;
+ default:
+ break;
+ }
+
+ val = spm_legacy_mode << 2 | qchannel_ignore << 1;
+ __raw_writel(val, dev->q2s_reg);
+ mb();
+}
+
+static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
+ unsigned int mode, bool notify_rpm)
+{
+ uint32_t i;
+ uint32_t start_addr = 0;
+ int ret = -EINVAL;
+ bool pc_mode = false;
+
+ if (!dev->initialized)
+ return -ENXIO;
+
+ if ((mode == MSM_SPM_MODE_POWER_COLLAPSE)
+ || (mode == MSM_SPM_MODE_GDHS))
+ pc_mode = true;
+
+ if (mode == MSM_SPM_MODE_DISABLED) {
+ ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
+ } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
+ for (i = 0; i < dev->num_modes; i++) {
+ if ((dev->modes[i].mode == mode) &&
+ (dev->modes[i].notify_rpm == notify_rpm)) {
+ start_addr = dev->modes[i].start_addr;
+ break;
+ }
+ }
+ ret = msm_spm_drv_set_low_power_mode(&dev->reg_data,
+ start_addr, pc_mode);
+ }
+
+ msm_spm_config_q2s(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(
+ sizeof(struct msm_spm_power_modes) * dev->num_modes,
+ GFP_KERNEL);
+
+ if (!dev->modes)
+ goto spm_failed_malloc;
+
+ dev->reg_data.ver_reg = data->ver_reg;
+ 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].start_addr = offset;
+ 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;
+ dev->modes[i].notify_rpm = data->modes[i].notify_rpm;
+ }
+ msm_spm_drv_reinit(&dev->reg_data);
+ 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
+ * @base: The SAW VCTL register which would set the voltage up.
+ * @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(void __iomem *base, unsigned int val, int cpu)
+{
+ uint32_t timeout = 2000; /* delay for voltage to settle on the core */
+ struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
+
+ /*
+ * If clock drivers have already set up the voltage,
+ * do not overwrite that value.
+ */
+ if (dev && (dev->cpu_vdd != VDD_DEFAULT))
+ return 0;
+
+ /* Set the CPU supply regulator voltage */
+ val = (val & 0xFF);
+ writel_relaxed(val, base);
+ mb();
+ udelay(timeout);
+
+ /* Enable the CPU supply regulator*/
+ val = 0x30080;
+ writel_relaxed(val, base);
+ mb();
+ udelay(timeout);
+
+ 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));
+}
+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 = &__get_cpu_var(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_set_low_power_mode() - Configure SPM start address for low power mode
+ * @mode: SPM LPM mode to enter
+ * @notify_rpm: Notify RPM in this mode
+ */
+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
+{
+ struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
+
+ return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
+}
+EXPORT_SYMBOL(msm_spm_set_low_power_mode);
+
+/**
+ * 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;
+
+ BUG_ON((nr_devs < num_possible_cpus()) || !data);
+
+ 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);
+}
+#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;
+ int ret = -EINVAL;
+ 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 {
+ char *key = "qcom,core-id";
+
+ ret = of_property_read_u32(node, key, &cpu);
+ if (!ret)
+ return cpu;
+ }
+ return ret;
+}
+
+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 == 0xffff) || (cpu < 0))
+ 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 long vctl_mask = 0;
+ unsigned c = 0;
+ int idx = 0;
+ struct device_node *cpu_node = NULL;
+ int ret = 0;
+ char *key = "qcom,cpu-vctl-list";
+ bool found = false;
+
+ cpu_node = of_parse_phandle(node, key, idx++);
+ while (cpu_node) {
+ found = true;
+ 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++);
+ };
+
+ if (found)
+ return;
+
+ key = "qcom,cpu-vctl-mask";
+ ret = of_property_read_u32(node, key, (u32 *) &vctl_mask);
+ if (!ret) {
+ for_each_set_bit(c, &vctl_mask, num_possible_cpus()) {
+ cpumask_set_cpu(c, mask);
+ }
+ }
+}
+
+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 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_SAW2_CFG},
+ {"qcom,saw2-avs-ctl", MSM_SPM_REG_SAW2_AVS_CTL},
+ {"qcom,saw2-avs-hysteresis", MSM_SPM_REG_SAW2_AVS_HYSTERESIS},
+ {"qcom,saw2-avs-limit", MSM_SPM_REG_SAW2_AVS_LIMIT},
+ {"qcom,saw2-avs-dly", MSM_SPM_REG_SAW2_AVS_DLY},
+ {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY},
+ {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL},
+ {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0},
+ {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1},
+ {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2},
+ {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3},
+ {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4},
+ {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5},
+ {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6},
+ {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7},
+ };
+
+ struct mode_of {
+ char *key;
+ uint32_t id;
+ uint32_t notify_rpm;
+ };
+
+ struct mode_of mode_of_data[] = {
+ {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0},
+ {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0},
+ {"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1},
+ {"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0},
+ {"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1},
+ };
+
+ dev = msm_spm_get_device(pdev);
+ if (!dev) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ 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.phase_port = -1;
+ spm_data.pfm_port = -1;
+
+ key = "qcom,vctl-port";
+ of_property_read_u32(node, key, &spm_data.vctl_port);
+
+ 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(pdev, IORESOURCE_MEM, 1);
+ 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;
+ }
+ }
+ /*
+ * 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);
+
+ 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;
+ spm_data.reg_init_values[spm_of_data[i].id] = val;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
+ key = mode_of_data[i].key;
+ modes[mode_count].cmd =
+ (uint8_t *)of_get_property(node, key, &len);
+ if (!modes[mode_count].cmd)
+ continue;
+ modes[mode_count].mode = mode_of_data[i].id;
+ modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm;
+ pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__,
+ dev->name, key, modes[mode_count].mode,
+ modes[mode_count].notify_rpm);
+ mode_count++;
+ }
+
+ spm_data.modes = modes;
+ spm_data.num_modes = mode_count;
+
+ ret = msm_spm_dev_init(dev, &spm_data);
+ if (ret)
+ goto fail;
+
+ platform_set_drvdata(pdev, dev);
+
+ for_each_cpu(cpu, &dev->mask)
+ per_cpu(cpu_vctl_device, cpu) = dev;
+
+ 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 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",
+ .owner = THIS_MODULE,
+ .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);
+}
+device_initcall(msm_spm_device_init);
new file mode 100644
@@ -0,0 +1,118 @@
+/* Copyright (c) 2011-2014, 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.
+ */
+#ifndef __ARCH_ARM_MACH_MSM_SPM_DRIVER_H
+#define __ARCH_ARM_MACH_MSM_SPM_DRIVER_H
+
+#include <soc/qcom/spm.h>
+
+enum {
+ MSM_SPM_REG_SAW2_CFG,
+ MSM_SPM_REG_SAW2_AVS_CTL,
+ MSM_SPM_REG_SAW2_AVS_HYSTERESIS,
+ MSM_SPM_REG_SAW2_SPM_CTL,
+ MSM_SPM_REG_SAW2_PMIC_DLY,
+ MSM_SPM_REG_SAW2_AVS_LIMIT,
+ MSM_SPM_REG_SAW2_AVS_DLY,
+ MSM_SPM_REG_SAW2_SPM_DLY,
+ MSM_SPM_REG_SAW2_PMIC_DATA_0,
+ MSM_SPM_REG_SAW2_PMIC_DATA_1,
+ MSM_SPM_REG_SAW2_PMIC_DATA_2,
+ MSM_SPM_REG_SAW2_PMIC_DATA_3,
+ MSM_SPM_REG_SAW2_PMIC_DATA_4,
+ MSM_SPM_REG_SAW2_PMIC_DATA_5,
+ MSM_SPM_REG_SAW2_PMIC_DATA_6,
+ MSM_SPM_REG_SAW2_PMIC_DATA_7,
+ MSM_SPM_REG_SAW2_RST,
+
+ MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST,
+
+ MSM_SPM_REG_SAW2_ID,
+ MSM_SPM_REG_SAW2_SECURE,
+ MSM_SPM_REG_SAW2_STS0,
+ MSM_SPM_REG_SAW2_STS1,
+ MSM_SPM_REG_SAW2_STS2,
+ MSM_SPM_REG_SAW2_VCTL,
+ MSM_SPM_REG_SAW2_SEQ_ENTRY,
+ MSM_SPM_REG_SAW2_SPM_STS,
+ MSM_SPM_REG_SAW2_AVS_STS,
+ MSM_SPM_REG_SAW2_PMIC_STS,
+ MSM_SPM_REG_SAW2_VERSION,
+
+ MSM_SPM_REG_NR,
+};
+
+struct msm_spm_seq_entry {
+ uint32_t mode;
+ uint8_t *cmd;
+ bool notify_rpm;
+};
+
+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;
+ 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;
+ 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);
+void msm_spm_drv_reinit(struct msm_spm_driver_data *dev);
+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
+ uint32_t addr, bool pc_mode);
+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);
+
+void msm_spm_reinit(void);
+int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
+
+#endif
new file mode 100644
@@ -0,0 +1,106 @@
+/* Copyright (c) 2010-2014, 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.
+ */
+
+#ifndef __MSM_SPM_H
+#define __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_NR
+};
+
+struct msm_spm_device;
+
+#if defined(CONFIG_QCOM_PM)
+
+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm);
+int msm_spm_probe_done(void);
+int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel);
+unsigned int msm_spm_get_vdd(unsigned int cpu);
+int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu);
+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_device_init(void);
+bool msm_spm_is_mode_avail(unsigned int mode);
+void msm_spm_dump_regs(unsigned int cpu);
+int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt);
+int msm_spm_enable_fts_lpm(int cpu, uint32_t mode);
+
+#else /* defined(CONFIG_QCOM_PM) */
+static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
+{
+ return -ENOSYS;
+}
+
+static inline int msm_spm_probe_done(void)
+{
+ return -ENOSYS;
+}
+
+static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
+{
+ return -ENOSYS;
+}
+
+static inline unsigned int msm_spm_get_vdd(unsigned int cpu)
+{
+ return 0;
+}
+
+static inline int msm_spm_turn_on_cpu_rail(void __iomem *base,
+ unsigned int val, int cpu)
+{
+ return -ENOSYS;
+}
+
+static inline int msm_spm_device_init(void)
+{
+ return -ENOSYS;
+}
+
+static 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 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_apcs_set_phase(int cpu, unsigned int phase_cnt)
+{
+ return -ENOSYS;
+}
+
+static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
+{
+ return -ENOSYS;
+}
+
+#endif /* defined (CONFIG_QCOM_PM) */
+#endif /* __MSM_SPM_H */