On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.i...@linaro.org> wrote:

> Qualcomm chipsets use an separate h/w block to control the logic around
> the processor cores (cpu and L2). The SPM h/w block regulates power to
> the cores and controls the power when the core enter low power modes.
> 
> Each core has its own instance of SPM. The SPM has the following key
> functions
>       - Configure the h/w dependencies when entering low power modes
>       - Wait for interrupt and wake up on interrupt
>       - Ensure the dependencies are ready before bringing the core out
>         of sleep
>       - Regulating voltage to the core, interfacing with the PMIC.
>       - Optimize power based on runtime recommendations.
> 
> The driver identifies and configures the SPMs, by reading the nodes and
> the register values from the devicetree. The SPMs need to be configured
> to allow the processor to be idled in a low power state.
> 
> Signed-off-by: Praveen Chidamabram <pchid...@codeaurora.org>
> Signed-off-by: Murali Nalajala <mnala...@codeaurora.org>
> Signed-off-by: Lina Iyer <lina.i...@linaro.org>
> ---
> .../devicetree/bindings/arm/msm/spm-v2.txt         |  62 ++
> drivers/soc/qcom/Makefile                          |   2 +
> drivers/soc/qcom/spm-devices.c                     | 703 +++++++++++++++++++++
> drivers/soc/qcom/spm.c                             | 482 ++++++++++++++
> drivers/soc/qcom/spm_driver.h                      | 116 ++++
> include/soc/qcom/spm.h                             |  70 ++
> 6 files changed, 1435 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> create mode 100644 drivers/soc/qcom/spm-devices.c
> create mode 100644 drivers/soc/qcom/spm.c
> create mode 100644 drivers/soc/qcom/spm_driver.h
> create mode 100644 include/soc/qcom/spm.h
> 
> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt 
> b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> new file mode 100644
> index 0000000..3130f4b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt
> @@ -0,0 +1,62 @@
> +* 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 devicetree representation of the SPM block should be:
> +
> +Required properties
> +
> +- compatible: Could be one of -
> +             "qcom,spm-v2.1"
> +             "qcom,spm-v3.0"
> +- 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,saw2-cfg: SAW2 configuration register
> +- 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,name: The name with which a SPM device is identified by the power
> +     management code.

Still not sure about this, wondering why we can’t use the node name.

> +
> +Optional properties
> +
> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
> +     (Fast Transient Switch) 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. This sequence 
> may
> +     turn off other SoC components.
> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
> +     sequence. This sequence will retain the memory but turn off the logic.
> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
> +     can control.
> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for 
> voltage to
> +     change after sending the voltage command to the PMIC.
> +-
> +Example:
> +     qcom,spm@f9089000 {
> +             compatible = "qcom,spm-v2";
> +             #address-cells = <1>;
> +             #size-cells = <1>;
> +             reg = <0xf9089000 0x1000>;
> +             qcom,cpu = <&CPU0>;
> +             qcom,saw2-cfg = <0x1>;
> +             qcom,saw2-spm-dly= <0x20000400>;
> +             qcom,saw2-spm-ctl = <0x1>;
> +             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];
> +     };
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index 70d52ed..d7ae93b 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -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
> diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
> new file mode 100644
> index 0000000..567e9f9
> --- /dev/null
> +++ b/drivers/soc/qcom/spm-devices.c
> @@ -0,0 +1,703 @@
> +/* 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);
> +}

Where is this used?

> +
> +/**
> + * 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.major = data->major;
> +     dev->reg_data.minor = data->minor;
> +     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;
> +             }
> +     }
> +     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);
> +             }
> +     }

kill this code if we really dropped the ‘qcom,cpu-vctl-mask’ DT support.

> +}
> +
> +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-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));
> +
> +     if (of_device_is_compatible(node, "qcom,spm-v2.1")) {
> +             spm_data.major = 2;
> +             spm_data.minor = 1;
> +     } else if (of_device_is_compatible(node, "qcom,spm-v3.0")) {
> +             spm_data.major = 3;
> +             spm_data.minor = 0;
> +     }
> +
> +     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.1"},
> +     {.compatible = "qcom,spm-v3.0"},
> +     {},
> +};
> +
> +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);
> diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
> new file mode 100644
> index 0000000..7dbdb64
> --- /dev/null
> +++ b/drivers/soc/qcom/spm.c
> @@ -0,0 +1,482 @@
> +/* 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,
> +};

I don’t see what having these arrays provides as the only differences are that 
v3.0 has MSM_SPM_REG_SAW2_STS2 and the offset of MSM_SPM_REG_SAW2_SEQ_ENTRY.  
If so we can remove all this extra code and just add a simple check in 
msm_spm_drv_flush_seq_entry that looks at the compatible and picks the proper 
offset when updating MSM_SPM_REG_SAW2_SEQ_ENTRY.

> +
> +static struct saw2_data saw2_info[] = {
> +     [0] = {
> +             "SAW2_v2.1",
> +             2,
> +             1,
> +             msm_spm_reg_offsets_saw2_v2_1,
> +     },
> +     [1] = {
> +             "SAW2_v3.0",
> +             3,
> +             0,
> +             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]);
> +}

have you looked at regmap and if that can accomplish the same goal as what this 
shadow stuff is doing?

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

This should probably be something like __msm_spm_dump_regs()

> +{
> +     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)
> +{
> +
> +     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;
> +}
> +
> +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
> +{
> +     uint32_t timeout_us, new_level;
> +
> +     if (!dev)
> +             return -EINVAL;
> +
> +     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);
> +
> +     /* 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);
> +
> +     return 0;
> +
> +set_vdd_bail:
> +     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;
> +
> +     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;
> +}
> diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h
> new file mode 100644
> index 0000000..b306520
> --- /dev/null
> +++ b/drivers/soc/qcom/spm_driver.h
> @@ -0,0 +1,116 @@
> +/* 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 __QCOM_SPM_DRIVER_H
> +#define __QCOM_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 major;
> +     uint32_t minor;
> +     uint32_t vctl_port;
> +     uint32_t phase_port;
> +     uint32_t pfm_port;
> +
> +     uint8_t awake_vlevel;
> +     uint32_t vctl_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 vctl_port;
> +     uint32_t phase_port;
> +     uint32_t pfm_port;
> +     void __iomem *reg_base_addr;
> +     uint32_t vctl_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 /* __QCOM_SPM_DRIVER_H */
> diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
> new file mode 100644
> index 0000000..f39e0c4
> --- /dev/null
> +++ b/include/soc/qcom/spm.h
> @@ -0,0 +1,70 @@
> +/* 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 __QCOM_SPM_H
> +#define __QCOM_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)

Where is CONFIG_QCOM_PM defined?  Wondering if we should have a CONFIG_QCOM_SPM 
and it can depend on any future ‘QCOM_PM’ in the Kconfig.

> +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  /* __QCOM_SPM_H */
> -- 
> 1.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
> the body of a message to majord...@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by 
The Linux Foundation

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to