From mboxrd@z Thu Jan 1 00:00:00 1970 From: Lina Iyer 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 Message-ID: <20140813140033.GA26905@ilina-mac> References: <1407872640-6732-1-git-send-email-lina.iyer@linaro.org> <1407872640-6732-4-git-send-email-lina.iyer@linaro.org> <53EB42B3.4020307@linaro.org> Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: QUOTED-PRINTABLE Return-path: Received: from mail-ig0-f170.google.com ([209.85.213.170]:63073 "EHLO mail-ig0-f170.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750757AbaHMOAp (ORCPT ); Wed, 13 Aug 2014 10:00:45 -0400 Received: by mail-ig0-f170.google.com with SMTP id h3so11265047igd.1 for ; Wed, 13 Aug 2014 07:00:44 -0700 (PDT) Content-Disposition: inline In-Reply-To: <53EB42B3.4020307@linaro.org> Sender: linux-arm-msm-owner@vger.kernel.org List-Id: linux-arm-msm@vger.kernel.org To: Daniel Lezcano 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 , Murali Nalajala 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 arou= nd >>the processor cores (cpu and L2). The SPM h/w block regulates power t= o >>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 a= nd >>the register values from the devicetree. The SPMs need to be configur= ed >>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=20 >comments for this patch and because of its size, it will be impossible= =20 >to follow a discussion. This patch is really too big to review, please= =20 >split it into smaller chunks. > >Thanks > > -- Daniel Hmm.. Tedious, but not impossible. Will get right on it. > >>Signed-off-by: Praveen Chidamabram >>Signed-off-by: Murali Nalajala >>Signed-off-by: Lina Iyer >>--- >> .../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= =2Etxt >> 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/D= ocumentation/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 Applic= ation >>+Processor Sub-System power. These SPM blocks run individual state ma= chine >>+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. O= n targets >>+ that dont support CPU phandles the driver would support qcom,core-i= d. >>+ 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 i= n the SPM >>+ sequence >>+- qcom,saw2-spm-ctl: The SPM control register >>+- qcom,name: The name with which a SPM device is identified by the p= ower >>+ management code. >>+ >>+Optional properties >>+ >>+- qcom,saw2-pmic-data0..7: Specify the pmic data value and the assoc= iated FTS >>+ (Fast Transient Switch) index to send the PMIC data to >>+- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for chang= ing >>+ voltage >>+- qcom,phase-port: The PVC port used for changing the number of phas= es >>+- 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 w= here APPS >>+ proc won't inform the RPM. >>+- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This se= quence may >>+ turn off other SoC components. >>+- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) co= mmand >>+ sequence. This sequence will retain the memory but turn off the log= ic. >>+- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the s= pm device >>+ can control. >>+- qcom,vctl-timeout-us: The timeout value in microseconds to wait fo= r voltage to >>+ change after sending the voltage command to the PMIC. >>+- >>+Example: >>+ qcom,spm@f9089000 { >>+ compatible =3D "qcom,spm-v2"; >>+ #address-cells =3D <1>; >>+ #size-cells =3D <1>; >>+ reg =3D <0xf9089000 0x1000>; >>+ qcom,cpu =3D <&CPU0>; >>+ qcom,saw2-cfg =3D <0x1>; >>+ qcom,saw2-spm-dly=3D <0x20000400>; >>+ qcom,saw2-spm-ctl =3D <0x1>; >>+ qcom,saw2-spm-cmd-wfi =3D [03 0b 0f]; >>+ qcom,saw2-spm-cmd-spc =3D [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) +=3D qcom_gsbi.o >>+obj-$(CONFIG_QCOM_PM) +=3D spm-devices.o spm.o >>+ >> CFLAGS_scm.o :=3D$(call as-instr,.arch_extension sec,-DREQUIRES_SEC= =3D1) >> obj-$(CONFIG_QCOM_SCM) +=3D scm.o scm-boot.o >>diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-de= vices.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 reserve= d. >>+ * >>+ * This program is free software; you can redistribute it and/or mod= ify >>+ * it under the terms of the GNU General Public License version 2 an= d >>+ * only version 2 as published by the Free Software Foundation. >>+ * >>+ * This program is distributed in the hope that it will be useful, >>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of >>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>+ * GNU General Public License for more details. >>+ * >>+ */ >>+ >>+#include >>+#include >>+#include >>+#include >>+#include >>+#include >>+#include >>+#include >>+#include >>+#include >>+#include >>+ >>+#include >>+ >>+#include "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 =3D (struct msm_spm_vdd_info *)data; >>+ struct msm_spm_device *dev =3D info->vctl_dev; >>+ >>+ dev->cpu_vdd =3D info->vlevel; >>+ info->err =3D 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 -EPROB= E_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 =3D 0; >>+ >>+ for_each_possible_cpu(cpu) { >>+ dev =3D per_cpu(cpu_vctl_device, cpu); >>+ if (!dev) >>+ return -EPROBE_DEFER; >>+ >>+ ret =3D 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=20 >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 =3D per_cpu(cpu_vctl_device, cpu); >>+ int ret; >>+ >>+ if (!dev) >>+ return -EPROBE_DEFER; >>+ >>+ ret =3D IS_ERR(dev); >>+ if (ret) >>+ return ret; >>+ >>+ info.vctl_dev =3D dev; >>+ info.vlevel =3D vlevel; >>+ >>+ ret =3D smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &inf= o, >>+ 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 =3D per_cpu(cpu_vctl_device, cpu); >>+ >>+ if (!dev) >>+ return -EPROBE_DEFER; >>+ >>+ ret =3D 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 =3D 0; >>+ uint32_t qchannel_ignore =3D 0; >>+ uint32_t val =3D 0; >>+ >>+ if (!dev->q2s_reg) >>+ return; >>+ >>+ switch (mode) { >>+ case MSM_SPM_MODE_DISABLED: >>+ case MSM_SPM_MODE_CLOCK_GATING: >>+ qchannel_ignore =3D 1; >>+ spm_legacy_mode =3D 0; >>+ break; >>+ case MSM_SPM_MODE_RETENTION: >>+ qchannel_ignore =3D 0; >>+ spm_legacy_mode =3D 0; >>+ break; >>+ case MSM_SPM_MODE_GDHS: >>+ case MSM_SPM_MODE_POWER_COLLAPSE: >>+ qchannel_ignore =3D 0; >>+ spm_legacy_mode =3D 1; >>+ break; >>+ default: >>+ break; >>+ } >>+ >>+ val =3D 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 =3D 0; >>+ int ret =3D -EINVAL; >>+ bool pc_mode =3D false; >>+ >>+ if (!dev->initialized) >>+ return -ENXIO; >>+ >>+ if ((mode =3D=3D MSM_SPM_MODE_POWER_COLLAPSE) >>+ || (mode =3D=3D MSM_SPM_MODE_GDHS)) >>+ pc_mode =3D true; >>+ >>+ if (mode =3D=3D MSM_SPM_MODE_DISABLED) { >>+ ret =3D msm_spm_drv_set_spm_enable(&dev->reg_data, false); >>+ } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) { >>+ for (i =3D 0; i < dev->num_modes; i++) { >>+ if ((dev->modes[i].mode =3D=3D mode) && >>+ (dev->modes[i].notify_rpm =3D=3D notify_rpm)) { >>+ start_addr =3D dev->modes[i].start_addr; >>+ break; >>+ } >>+ } >>+ ret =3D 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 =3D -ENOMEM; >>+ uint32_t offset =3D 0; >>+ >>+ dev->cpu_vdd =3D VDD_DEFAULT; >>+ dev->num_modes =3D data->num_modes; >>+ dev->modes =3D kmalloc( >>+ sizeof(struct msm_spm_power_modes) * dev->num_modes, >>+ GFP_KERNEL); >>+ >>+ if (!dev->modes) >>+ goto spm_failed_malloc; >>+ >>+ dev->reg_data.major =3D data->major; >>+ dev->reg_data.minor =3D data->minor; >>+ ret =3D msm_spm_drv_init(&dev->reg_data, data); >>+ >>+ if (ret) >>+ goto spm_failed_init; >>+ >>+ for (i =3D 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 =3D offset; >>+ ret =3D 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 =3D data->modes[i].mode; >>+ dev->modes[i].notify_rpm =3D data->modes[i].notify_rpm; >>+ } >>+ msm_spm_drv_reinit(&dev->reg_data); >>+ dev->initialized =3D 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 c= ore >>+ * @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, i= nt cpu) >>+{ >>+ uint32_t timeout =3D 2000; /* delay for voltage to settle on the co= re */ >>+ struct msm_spm_device *dev =3D 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 !=3D VDD_DEFAULT)) >>+ return 0; >>+ >>+ /* Set the CPU supply regulator voltage */ >>+ val =3D (val & 0xFF); >>+ writel_relaxed(val, base); >>+ mb(); >>+ udelay(timeout); >>+ >>+ /* Enable the CPU supply regulator*/ >>+ val =3D 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 th= e cpu >>+ * It should only be used to decide a mode before lpm driver is prob= ed. >>+ * @mode: SPM LPM mode to be selected >>+ */ >>+bool msm_spm_is_mode_avail(unsigned int mode) >>+{ >>+ struct msm_spm_device *dev =3D &__get_cpu_var(msm_cpu_spm_device); >>+ int i; >>+ >>+ for (i =3D 0; i < dev->num_modes; i++) { >>+ if (dev->modes[i].mode =3D=3D mode) >>+ return true; >>+ } >>+ >>+ return false; >>+} >>+ >>+/** >>+ * msm_spm_set_low_power_mode() - Configure SPM start address for lo= w 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 =3D &__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_d= evs) >>+{ >>+ unsigned int cpu; >>+ int ret =3D 0; >>+ >>+ BUG_ON((nr_devs < num_possible_cpus()) || !data); >>+ >>+ for_each_possible_cpu(cpu) { >>+ struct msm_spm_device *dev =3D &per_cpu(msm_cpu_spm_device, cpu); >>+ >>+ ret =3D 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 >>+ =3D 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 =3D 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 =3D 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 =3D -EINVAL; >>+ char *key =3D "qcom,cpu"; >>+ >>+ cpu_node =3D of_parse_phandle(node, key, 0); >>+ if (cpu_node) { >>+ for_each_possible_cpu(cpu) { >>+ if (of_get_cpu_node(cpu, NULL) =3D=3D cpu_node) >>+ return cpu; >>+ } >>+ } >>+ return ret; >>+} >>+ >>+static struct msm_spm_device *msm_spm_get_device(struct platform_dev= ice *pdev) >>+{ >>+ struct msm_spm_device *dev =3D NULL; >>+ const char *val =3D NULL; >>+ char *key =3D "qcom,name"; >>+ int cpu =3D get_cpu_id(pdev->dev.of_node); >>+ >>+ if ((cpu >=3D 0) && cpu < num_possible_cpus()) >>+ dev =3D &per_cpu(msm_cpu_spm_device, cpu); >>+ else if ((cpu =3D=3D 0xffff) || (cpu < 0)) >>+ dev =3D 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 =3D 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 *ma= sk) >>+{ >>+ unsigned long vctl_mask =3D 0; >>+ unsigned c =3D 0; >>+ int idx =3D 0; >>+ struct device_node *cpu_node =3D NULL; >>+ int ret =3D 0; >>+ char *key =3D "qcom,cpu-vctl-list"; >>+ bool found =3D false; >>+ >>+ cpu_node =3D of_parse_phandle(node, key, idx++); >>+ while (cpu_node) { >>+ found =3D true; >>+ for_each_possible_cpu(c) { >>+ if (of_get_cpu_node(c, NULL) =3D=3D cpu_node) >>+ cpumask_set_cpu(c, mask); >>+ } >>+ cpu_node =3D of_parse_phandle(node, key, idx++); >>+ }; >>+ >>+ if (found) >>+ return; >>+ >>+ key =3D "qcom,cpu-vctl-mask"; >>+ ret =3D 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 =3D 0; >>+ int cpu =3D 0; >>+ int i =3D 0; >>+ struct device_node *node =3D pdev->dev.of_node; >>+ struct msm_spm_platform_data spm_data; >>+ char *key =3D NULL; >>+ uint32_t val =3D 0; >>+ struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR]; >>+ int len =3D 0; >>+ struct msm_spm_device *dev =3D NULL; >>+ struct resource *res =3D NULL; >>+ uint32_t mode_count =3D 0; >>+ >>+ struct spm_of { >>+ char *key; >>+ uint32_t id; >>+ }; >>+ >>+ struct spm_of spm_of_data[] =3D { >>+ {"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[] =3D { >>+ {"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 =3D msm_spm_get_device(pdev); >>+ if (!dev) { >>+ ret =3D -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 =3D 2; >>+ spm_data.minor =3D 1; >>+ } else if (of_device_is_compatible(node, "qcom,spm-v3.0")) { >>+ spm_data.major =3D 3; >>+ spm_data.minor =3D 0; >>+ } >>+ >>+ key =3D "qcom,vctl-timeout-us"; >>+ ret =3D of_property_read_u32(node, key, &val); >>+ if (!ret) >>+ spm_data.vctl_timeout_us =3D val; >>+ >>+ /* SAW start address */ >>+ res =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); >>+ if (!res) { >>+ ret =3D -EFAULT; >>+ goto fail; >>+ } >>+ >>+ spm_data.reg_base_addr =3D devm_ioremap(&pdev->dev, res->start, >>+ resource_size(res)); >>+ if (!spm_data.reg_base_addr) { >>+ ret =3D -ENOMEM; >>+ goto fail; >>+ } >>+ >>+ spm_data.vctl_port =3D -1; >>+ spm_data.phase_port =3D -1; >>+ spm_data.pfm_port =3D -1; >>+ >>+ key =3D "qcom,vctl-port"; >>+ of_property_read_u32(node, key, &spm_data.vctl_port); >>+ >>+ key =3D "qcom,phase-port"; >>+ of_property_read_u32(node, key, &spm_data.phase_port); >>+ >>+ key =3D "qcom,pfm-port"; >>+ of_property_read_u32(node, key, &spm_data.pfm_port); >>+ >>+ /* Q2S (QChannel-2-SPM) register */ >>+ res =3D platform_get_resource(pdev, IORESOURCE_MEM, 1); >>+ if (res) { >>+ dev->q2s_reg =3D devm_ioremap(&pdev->dev, res->start, >>+ resource_size(res)); >>+ if (!dev->q2s_reg) { >>+ pr_err("%s(): Unable to iomap Q2S register\n", >>+ __func__); >>+ ret =3D -EADDRNOTAVAIL; >>+ goto fail; >>+ } >>+ } >>+ /* >>+ * At system boot, cpus and or clusters can remain in reset. CCI SP= M >>+ * 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 =3D 0; i < ARRAY_SIZE(spm_of_data); i++) { >>+ ret =3D of_property_read_u32(node, spm_of_data[i].key, &val); >>+ if (ret) >>+ continue; >>+ spm_data.reg_init_values[spm_of_data[i].id] =3D val; >>+ } >>+ >>+ for (i =3D 0; i < ARRAY_SIZE(mode_of_data); i++) { >>+ key =3D mode_of_data[i].key; >>+ modes[mode_count].cmd =3D >>+ (uint8_t *)of_get_property(node, key, &len); >>+ if (!modes[mode_count].cmd) >>+ continue; >>+ modes[mode_count].mode =3D mode_of_data[i].id; >>+ modes[mode_count].notify_rpm =3D 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 =3D modes; >>+ spm_data.num_modes =3D mode_count; >>+ >>+ ret =3D 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) =3D dev; >>+ >>+ return ret; >>+ >>+fail: >>+ cpu =3D get_cpu_id(pdev->dev.of_node); >>+ if (dev && (cpu >=3D num_possible_cpus() || (cpu < 0))) { >>+ for_each_cpu(cpu, &dev->mask) >>+ per_cpu(cpu_vctl_device, cpu) =3D ERR_PTR(ret); >>+ } >>+ >>+ pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, re= t); >>+ >>+ return ret; >>+} >>+ >>+static int msm_spm_dev_remove(struct platform_device *pdev) >>+{ >>+ struct msm_spm_device *dev =3D platform_get_drvdata(pdev); >>+ >>+ list_del(&dev->list); >>+ >>+ return 0; >>+} >>+ >>+static struct of_device_id msm_spm_match_table[] =3D { >>+ {.compatible =3D "qcom,spm-v2.1"}, >>+ {.compatible =3D "qcom,spm-v3.0"}, >>+ {}, >>+}; >>+ >>+static struct platform_driver msm_spm_device_driver =3D { >>+ .probe =3D msm_spm_dev_probe, >>+ .remove =3D msm_spm_dev_remove, >>+ .driver =3D { >>+ .name =3D "spm-v2", >>+ .owner =3D THIS_MODULE, >>+ .of_match_table =3D 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 =3D 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 reserve= d. >>+ * >>+ * This program is free software; you can redistribute it and/or mod= ify >>+ * it under the terms of the GNU General Public License version 2 an= d >>+ * only version 2 as published by the Free Software Foundation. >>+ * >>+ * This program is distributed in the hope that it will be useful, >>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of >>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>+ * GNU General Public License for more details. >>+ * >>+ */ >>+ >>+#include >>+#include >>+#include >>+#include >>+#include >>+#include >>+ >>+#include "spm_driver.h" >>+ >>+#define MSM_SPM_PMIC_STATE_IDLE 0 >>+ >>+enum { >>+ MSM_SPM_DEBUG_SHADOW =3D 1U << 0, >>+ MSM_SPM_DEBUG_VCTL =3D 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] =3D { >>+ [MSM_SPM_REG_SAW2_SECURE] =3D 0x00, >>+ [MSM_SPM_REG_SAW2_ID] =3D 0x04, >>+ [MSM_SPM_REG_SAW2_CFG] =3D 0x08, >>+ [MSM_SPM_REG_SAW2_SPM_STS] =3D 0x0C, >>+ [MSM_SPM_REG_SAW2_AVS_STS] =3D 0x10, >>+ [MSM_SPM_REG_SAW2_PMIC_STS] =3D 0x14, >>+ [MSM_SPM_REG_SAW2_RST] =3D 0x18, >>+ [MSM_SPM_REG_SAW2_VCTL] =3D 0x1C, >>+ [MSM_SPM_REG_SAW2_AVS_CTL] =3D 0x20, >>+ [MSM_SPM_REG_SAW2_AVS_LIMIT] =3D 0x24, >>+ [MSM_SPM_REG_SAW2_AVS_DLY] =3D 0x28, >>+ [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] =3D 0x2C, >>+ [MSM_SPM_REG_SAW2_SPM_CTL] =3D 0x30, >>+ [MSM_SPM_REG_SAW2_SPM_DLY] =3D 0x34, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_0] =3D 0x40, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_1] =3D 0x44, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_2] =3D 0x48, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_3] =3D 0x4C, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_4] =3D 0x50, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_5] =3D 0x54, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_6] =3D 0x58, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_7] =3D 0x5C, >>+ [MSM_SPM_REG_SAW2_SEQ_ENTRY] =3D 0x80, >>+ [MSM_SPM_REG_SAW2_VERSION] =3D 0xFD0, >>+}; >>+ >>+static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] =3D { >>+ [MSM_SPM_REG_SAW2_SECURE] =3D 0x00, >>+ [MSM_SPM_REG_SAW2_ID] =3D 0x04, >>+ [MSM_SPM_REG_SAW2_CFG] =3D 0x08, >>+ [MSM_SPM_REG_SAW2_SPM_STS] =3D 0x0C, >>+ [MSM_SPM_REG_SAW2_AVS_STS] =3D 0x10, >>+ [MSM_SPM_REG_SAW2_PMIC_STS] =3D 0x14, >>+ [MSM_SPM_REG_SAW2_RST] =3D 0x18, >>+ [MSM_SPM_REG_SAW2_VCTL] =3D 0x1C, >>+ [MSM_SPM_REG_SAW2_AVS_CTL] =3D 0x20, >>+ [MSM_SPM_REG_SAW2_AVS_LIMIT] =3D 0x24, >>+ [MSM_SPM_REG_SAW2_AVS_DLY] =3D 0x28, >>+ [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] =3D 0x2C, >>+ [MSM_SPM_REG_SAW2_SPM_CTL] =3D 0x30, >>+ [MSM_SPM_REG_SAW2_SPM_DLY] =3D 0x34, >>+ [MSM_SPM_REG_SAW2_STS2] =3D 0x38, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_0] =3D 0x40, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_1] =3D 0x44, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_2] =3D 0x48, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_3] =3D 0x4C, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_4] =3D 0x50, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_5] =3D 0x54, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_6] =3D 0x58, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_7] =3D 0x5C, >>+ [MSM_SPM_REG_SAW2_SEQ_ENTRY] =3D 0x400, >>+ [MSM_SPM_REG_SAW2_VERSION] =3D 0xFD0, >>+}; >>+ >>+static struct saw2_data saw2_info[] =3D { >>+ [0] =3D { >>+ "SAW2_v2.1", >>+ 2, >>+ 1, >>+ msm_spm_reg_offsets_saw2_v2_1, >>+ }, >>+ [1] =3D { >>+ "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] =3D >>+ __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 &=3D 0x7F; >>+ addr <<=3D 4; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &=3D 0xFFFFF80F; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |=3D addr; >>+ >>+ if (dev->major !=3D 0x3) >>+ return; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &=3D 0xFFFEFFFF; >>+ if (pc_mode) >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |=3D 0x00010000; >>+} >>+ >>+static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_da= ta *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 =3D 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 |=3D vlevel; >>+ pmic_data |=3D (dev->vctl_port & 0x7) << 16; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &=3D ~0x700FF; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |=3D pmic_data; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &=3D ~0x700FF; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |=3D 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 =3D 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] &=3D ~0x1; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |=3D 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 =3D msm_spm_drv_get_num_spm_entry(dev); >>+ >>+ if (!dev) { >>+ __WARN(); >>+ return; >>+ } >>+ >>+ for (i =3D 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 =3D *offset / 4; >>+ uint8_t last_cmd; >>+ >>+ if (!cmd) >>+ return -EINVAL; >>+ >>+ while (1) { >>+ int i; >>+ >>+ cmd_w =3D 0; >>+ last_cmd =3D 0; >>+ cmd_w =3D dev->reg_seq_entry_shadow[offset_w]; >>+ >>+ for (i =3D (*offset % 4); i < 4; i++) { >>+ last_cmd =3D *(cmd++); >>+ cmd_w |=3D last_cmd << (i * 8); >>+ (*offset)++; >>+ if (last_cmd =3D=3D 0x0f) >>+ break; >>+ } >>+ >>+ dev->reg_seq_entry_shadow[offset_w++] =3D cmd_w; >>+ if (last_cmd =3D=3D 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 =3D 0; i < MSM_SPM_REG_NR; i++) >>+ pr_info("%s: reg %02x =3D 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 in= t 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] =3D 1; >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST); >>+ >>+ msm_spm_drv_set_vctl2(dev, vlevel); >>+ >>+ timeout_us =3D dev->vctl_timeout_us; >>+ /* Confirm the voltage we set was what hardware sent */ >>+ do { >>+ new_level =3D msm_spm_drv_get_sts_curr_pmic_data(dev); >>+ if (new_level =3D=3D 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 =3D -1; >>+ >>+ switch (port) { >>+ case MSM_SPM_PMIC_VCTL_PORT: >>+ index =3D dev->vctl_port; >>+ break; >>+ case MSM_SPM_PMIC_PHASE_PORT: >>+ index =3D dev->phase_port; >>+ break; >>+ case MSM_SPM_PMIC_PFM_PORT: >>+ index =3D 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 =3D 0; >>+ unsigned int timeout_us =3D 0; >>+ int index =3D 0; >>+ >>+ if (!msm_spm_pmic_arb_present(dev)) >>+ return -ENOSYS; >>+ >>+ index =3D msm_spm_drv_get_pmic_port(dev, port); >>+ if (index < 0) >>+ return -ENODEV; >>+ >>+ pmic_data |=3D data & 0xFF; >>+ pmic_data |=3D (index & 0x7) << 16; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &=3D ~0x700FF; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |=3D pmic_data; >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); >>+ mb(); >>+ >>+ timeout_us =3D 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) =3D=3D >>+ 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 =3D 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=20 >function ? > >>+ >>+ for (i =3D 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 =3D false; >>+ >>+ BUG_ON(!dev || !data); >>+ >>+ dev->vctl_port =3D data->vctl_port; >>+ dev->phase_port =3D data->phase_port; >>+ dev->pfm_port =3D data->pfm_port; >>+ dev->reg_base_addr =3D data->reg_base_addr; >>+ memcpy(dev->reg_shadow, data->reg_init_values, >>+ sizeof(data->reg_init_values)); >>+ >>+ dev->vctl_timeout_us =3D data->vctl_timeout_us; >>+ >>+ for (i =3D 0; i < ARRAY_SIZE(saw2_info); i++) >>+ if (dev->major =3D=3D saw2_info[i].major && >>+ dev->minor =3D=3D saw2_info[i].minor) { >>+ pr_debug("%s: Version found\n", >>+ saw2_info[i].ver_name); >>+ dev->reg_offsets =3D saw2_info[i].spm_reg_offset_ptr; >>+ found =3D true; >>+ break; >>+ } >>+ >>+ if (!found) { >>+ pr_err("%s: No SAW2 version found\n", __func__); >>+ BUG_ON(!found); >>+ } >>+ >>+ if (!num_pmic_data) >>+ num_pmic_data =3D msm_spm_drv_get_num_pmic_data(dev); >>+ >>+ num_spm_entry =3D msm_spm_drv_get_num_spm_entry(dev); >>+ >>+ dev->reg_seq_entry_shadow =3D >>+ 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_dri= ver.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 reserve= d. >>+ * >>+ * This program is free software; you can redistribute it and/or mod= ify >>+ * it under the terms of the GNU General Public License version 2 an= d >>+ * 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 >>+ >>+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 =3D 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 reserve= d. >>+ * >>+ * This program is free software; you can redistribute it and/or mod= ify >>+ * it under the terms of the GNU General Public License version 2 an= d >>+ * 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, i= nt 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 vle= vel) >>+{ 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_devic= e *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 */ >> > > >--=20 > Linaro.org =E2=94=82 Open source software fo= r ARM SoCs > >Follow Linaro: Facebook | > Twitter | > Blog >