From: Lina Iyer <lina.iyer@linaro.org>
To: Daniel Lezcano <daniel.lezcano@linaro.org>
Cc: khilman@linaro.org, amit.kucheria@linaro.org,
sboyd@codeaurora.org, davidb@codeaurora.org,
galak@codeaurora.org, linux-arm-msm@vger.kernel.org,
msivasub@codeaurora.org,
Praveen Chidamabram <pchidamb@codeaurora.org>,
Murali Nalajala <mnalajal@codeaurora.org>
Subject: Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets
Date: Wed, 13 Aug 2014 08:00:34 -0600 [thread overview]
Message-ID: <20140813140033.GA26905@ilina-mac> (raw)
In-Reply-To: <53EB42B3.4020307@linaro.org>
On Wed, Aug 13, 2014 at 12:49:23PM +0200, Daniel Lezcano wrote:
>On 08/12/2014 09:43 PM, Lina Iyer 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.
>
>I began to comment but I realize I have a lot of questions and
>comments for this patch and because of its size, it will be impossible
>to follow a discussion. This patch is really too big to review, please
>split it into smaller chunks.
>
>Thanks
>
> -- Daniel
Hmm.. Tedious, but not impossible. Will get right on it.
>
>>Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org>
>>Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org>
>>Signed-off-by: Lina Iyer <lina.iyer@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.
>>+
>>+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);
>
>Can you explain how this function is used by the caller ? When is it
>called ? What is its purpose and who is setting 'dev' to an ERR ?
>
>>+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;
>
>If cpu_vctl_device is per cpu, why a cpumask is used ?
>
>>+
>>+ 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;
>
>Is the string pointed by val always valid ?
>
>>+ 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-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);
>
>Why is needed this 'registered' thing ?
>
>Couldn't the msm_spm_device_init be removed and replaced by:
>
>module_platform_driver(msm_spm_device_driver);
>
>?
>
>>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,
>>+};
>>+
>>+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]);
>>+}
>>+
>>+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)
>>+{
>>+ 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_leve
>>+ 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();
>
>Why are needed the mb() after calling the msm_spm_drv_flush_shadow
>function ?
>
>>+
>>+ 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)
>>+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 */
>>
>
>
>--
> <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>
>Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
><http://twitter.com/#!/linaroorg> Twitter |
><http://www.linaro.org/linaro-blog/> Blog
>
next prev parent reply other threads:[~2014-08-13 14:00 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-08-12 19:43 [PATCH v2 00/10] QCOM 8074 cpuidle driver Lina Iyer
2014-08-12 19:43 ` [PATCH v2 01/10] msm: scm: Move scm-boot files to drivers/soc and include/soc Lina Iyer
2014-08-12 19:43 ` [PATCH v2 02/10] msm: scm: Add SCM warmboot flags for quad core targets Lina Iyer
2014-08-14 10:20 ` Pramod Gurav
2014-08-12 19:43 ` [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Lina Iyer
2014-08-13 10:49 ` Daniel Lezcano
2014-08-13 14:00 ` Lina Iyer [this message]
2014-08-14 13:01 ` Pramod Gurav
2014-08-14 15:18 ` Lina Iyer
2014-08-14 15:16 ` Kumar Gala
2014-08-14 15:27 ` Lina Iyer
2014-08-14 15:33 ` Kumar Gala
2014-08-14 16:09 ` Kumar Gala
2014-08-14 16:18 ` Lina Iyer
2014-08-14 16:41 ` Kumar Gala
2014-08-15 4:18 ` Lina Iyer
2014-08-15 13:42 ` Kumar Gala
2014-08-16 3:41 ` Lina Iyer
2014-08-12 19:43 ` [PATCH v2 04/10] soc: qcom: Add QCOM Power management config Lina Iyer
2014-08-13 9:36 ` Daniel Lezcano
2014-08-12 19:43 ` [PATCH v2 05/10] arm: qcom-msm8974: Add CPU phandles to CPU definitions Lina Iyer
2014-08-12 21:09 ` Kumar Gala
2014-08-14 10:04 ` Pramod Gurav
2014-08-12 19:43 ` [PATCH v2 06/10] arm: dts: qcom: Add SPM device bindings for 8974 Lina Iyer
2014-08-12 21:10 ` Kumar Gala
2014-08-12 21:32 ` Lina Iyer
2014-08-13 7:39 ` Ivan T. Ivanov
2014-08-12 19:43 ` [PATCH v2 07/10] qcom: msm-pm: Add cpu low power mode functions Lina Iyer
2014-08-13 11:18 ` Daniel Lezcano
2014-08-13 14:16 ` Lina Iyer
2014-08-14 14:24 ` Daniel Lezcano
2014-08-14 14:53 ` Lina Iyer
2014-08-14 16:11 ` Daniel Lezcano
2014-08-14 19:22 ` Lina Iyer
2014-08-15 0:01 ` Daniel Lezcano
2014-08-15 1:02 ` Lina Iyer
2014-08-14 13:38 ` Pramod Gurav
2014-08-14 14:43 ` Lina Iyer
2014-08-12 19:43 ` [PATCH v2 08/10] qcom: cpuidle: Add cpuidle driver for QCOM cpus Lina Iyer
2014-08-13 11:22 ` Daniel Lezcano
2014-08-13 14:03 ` Lina Iyer
2014-08-12 19:43 ` [PATCH v2 09/10] qcom: cpuidle: Config option to enable QCOM cpuidle driver Lina Iyer
2014-08-13 11:18 ` Daniel Lezcano
2014-08-12 19:44 ` [PATCH v2 10/10] qcom: cpuidle: Add cpuidle device nodes for 8974 chipset Lina Iyer
2014-08-13 1:52 ` [PATCH v2 00/10] QCOM 8074 cpuidle driver Stephen Boyd
2014-08-13 2:17 ` Lina Iyer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20140813140033.GA26905@ilina-mac \
--to=lina.iyer@linaro.org \
--cc=amit.kucheria@linaro.org \
--cc=daniel.lezcano@linaro.org \
--cc=davidb@codeaurora.org \
--cc=galak@codeaurora.org \
--cc=khilman@linaro.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=mnalajal@codeaurora.org \
--cc=msivasub@codeaurora.org \
--cc=pchidamb@codeaurora.org \
--cc=sboyd@codeaurora.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox