From mboxrd@z Thu Jan 1 00:00:00 1970 From: Daniel Lezcano Subject: Re: [PATCH v2 03/10] qcom: spm: Add Subsystem Power Manager (SPM) driver for QCOM chipsets Date: Wed, 13 Aug 2014 12:49:23 +0200 Message-ID: <53EB42B3.4020307@linaro.org> References: <1407872640-6732-1-git-send-email-lina.iyer@linaro.org> <1407872640-6732-4-git-send-email-lina.iyer@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-wg0-f41.google.com ([74.125.82.41]:40169 "EHLO mail-wg0-f41.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750751AbaHMKt0 (ORCPT ); Wed, 13 Aug 2014 06:49:26 -0400 Received: by mail-wg0-f41.google.com with SMTP id z12so11065815wgg.0 for ; Wed, 13 Aug 2014 03:49:25 -0700 (PDT) In-Reply-To: <1407872640-6732-4-git-send-email-lina.iyer@linaro.org> Sender: linux-arm-msm-owner@vger.kernel.org List-Id: linux-arm-msm@vger.kernel.org To: Lina Iyer , khilman@linaro.org, amit.kucheria@linaro.org, sboyd@codeaurora.org, davidb@codeaurora.org, galak@codeaurora.org, linux-arm-msm@vger.kernel.org Cc: msivasub@codeaurora.org, Praveen Chidamabram , Murali Nalajala 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 comments= =20 for this patch and because of its size, it will be impossible to follow= =20 a discussion. This patch is really too big to review, please split it=20 into smaller chunks. Thanks -- Daniel > 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 =46ollow Linaro: Facebook | Twitter | Blog