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: Thu, 14 Aug 2014 09:27:00 -0600 Message-ID: <20140814152658.GD33559@ilina-mac.local> 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-pa0-f52.google.com ([209.85.220.52]:53497 "EHLO mail-pa0-f52.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755127AbaHNP1F (ORCPT ); Thu, 14 Aug 2014 11:27:05 -0400 Received: by mail-pa0-f52.google.com with SMTP id bj1so1820816pad.11 for ; Thu, 14 Aug 2014 08:27:05 -0700 (PDT) Content-Disposition: inline In-Reply-To: Sender: linux-arm-msm-owner@vger.kernel.org List-Id: linux-arm-msm@vger.kernel.org To: Kumar Gala Cc: daniel.lezcano@linaro.org, khilman@linaro.org, amit.kucheria@linaro.org, sboyd@codeaurora.org, davidb@codeaurora.org, linux-arm-msm@vger.kernel.org, msivasub@codeaurora.org, Praveen Chidamabram , Murali Nalajala On Thu, Aug 14, 2014 at 10:16:15AM -0500, Kumar Gala wrote: > >On Aug 12, 2014, at 2:43 PM, Lina Iyer wrote: > >> Qualcomm chipsets use an separate h/w block to control the logic aro= und >> the processor cores (cpu and L2). The SPM h/w block regulates power = to >> the cores and controls the power when the core enter low power modes= =2E >> >> Each core has its own instance of SPM. The SPM has the following key >> functions >> - Configure the h/w dependencies when entering low power modes >> - Wait for interrupt and wake up on interrupt >> - Ensure the dependencies are ready before bringing the core out >> of sleep >> - Regulating voltage to the core, interfacing with the PMIC. >> - Optimize power based on runtime recommendations. >> >> The driver identifies and configures the SPMs, by reading the nodes = and >> the register values from the devicetree. The SPMs need to be configu= red >> to allow the processor to be idled in a low power state. >> >> 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.= txt >> create mode 100644 drivers/soc/qcom/spm-devices.c >> create mode 100644 drivers/soc/qcom/spm.c >> create mode 100644 drivers/soc/qcom/spm_driver.h >> create mode 100644 include/soc/qcom/spm.h >> >> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/= Documentation/devicetree/bindings/arm/msm/spm-v2.txt >> new file mode 100644 >> index 0000000..3130f4b >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt >> @@ -0,0 +1,62 @@ >> +* MSM Subsystem Power Manager (spm-v2) >> + >> +S4 generation of MSMs have SPM hardware blocks to control the Appli= cation >> +Processor Sub-System power. These SPM blocks run individual state m= achine >> +to determine what the core (L2 or Krait/Scorpion) would do when the= WFI >> +instruction is executed by the core. >> + >> +The devicetree representation of the SPM block should be: >> + >> +Required properties >> + >> +- compatible: Could be one of - >> + "qcom,spm-v2.1" >> + "qcom,spm-v3.0" >> +- reg: The physical address and the size of the SPM's memory mapped= registers >> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. = On targets >> + that dont support CPU phandles the driver would support qcom,core-= id. >> + This field is required on only for SPMs that control the CPU. >> +- qcom,saw2-cfg: SAW2 configuration register >> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command = in the SPM >> + sequence >> +- qcom,saw2-spm-ctl: The SPM control register >> +- qcom,name: The name with which a SPM device is identified by the = power >> + management code. > >Still not sure about this, wondering why we can=E2=80=99t use the node= name. At this point, without the SoC Idle frameworks code, we dont need this. I will prune this off from the next version. > >> + >> +Optional properties >> + >> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the asso= ciated FTS >> + (Fast Transient Switch) index to send the PMIC data to >> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for chan= ging >> + voltage >> +- qcom,phase-port: The PVC port used for changing the number of pha= ses >> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes >> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence >> +- qcom,saw2-spm-cmd-ret: The Retention command sequence >> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence >> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence = where APPS >> + proc won't inform the RPM. >> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This s= equence may >> + turn off other SoC components. >> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) c= ommand >> + sequence. This sequence will retain the memory but turn off the lo= gic. >> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the = spm device >> + can control. >> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait f= or 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=3D= 1) >> obj-$(CONFIG_QCOM_SCM) +=3D scm.o scm-boot.o >> diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-d= evices.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 reserv= ed. >> + * >> + * This program is free software; you can redistribute it and/or mo= dify >> + * it under the terms of the GNU General Public License version 2 a= nd >> + * 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 -EPRO= BE_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); >> + >> +void msm_spm_dump_regs(unsigned int cpu) >> +{ >> + dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu); >> +} > >Where is this used? > Debug code. Helpful in debugging crashes. Abort handlers usually call them. Helps identify the state of the SPM and thereby the core, when it crashes. >> + >> +/** >> + * 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, &in= fo, >> + true); >> + if (ret) >> + return ret; >> + >> + return info.err; >> +} >> +EXPORT_SYMBOL(msm_spm_set_vdd); >> + >> +/** >> + * msm_spm_get_vdd(): Get core voltage >> + * @cpu: core id >> + * @return: Returns encoded PMIC data. >> + */ >> +unsigned int msm_spm_get_vdd(unsigned int cpu) >> +{ >> + int ret; >> + struct msm_spm_device *dev =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 *de= v, >> + 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 = core >> + * @base: The SAW VCTL register which would set the voltage up. >> + * @val: The value to be set on the rail >> + * @cpu: The cpu for this with rail is being powered on >> + */ >> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, = int cpu) >> +{ >> + uint32_t timeout =3D 2000; /* delay for voltage to settle on the c= ore */ >> + 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 t= he cpu >> + * It should only be used to decide a mode before lpm driver is pro= bed. >> + * @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 l= ow 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_= devs) >> +{ >> + 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 mode= s >> + * @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_de= vice *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; >> + list_add(&dev->list, &spm_list); >> + >> + return dev; >> +} >> + >> +static void get_cpumask(struct device_node *node, struct cpumask *m= ask) >> +{ >> + 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); >> + } >> + } > >kill this code if we really dropped the =E2=80=98qcom,cpu-vctl-mask=E2= =80=99 DT support. Done. > >> +} >> + >> +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 S= PM >> + * 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, r= et); >> + >> + 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); >> 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 reserv= ed. >> + * >> + * This program is free software; you can redistribute it and/or mo= dify >> + * it under the terms of the GNU General Public License version 2 a= nd >> + * 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, >> +}; > >I don=E2=80=99t see what having these arrays provides as the only diff= erences are that v3.0 has MSM_SPM_REG_SAW2_STS2 and the offset of MSM_S= PM_REG_SAW2_SEQ_ENTRY. If so we can remove all this extra code and jus= t add a simple check in msm_spm_drv_flush_seq_entry that looks at the c= ompatible and picks the proper offset when updating MSM_SPM_REG_SAW2_SE= Q_ENTRY. > Isnt that an hack ? >> + >> +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 *de= v, >> + unsigned int reg_index) >> +{ >> + __raw_writel(dev->reg_shadow[reg_index], >> + dev->reg_base_addr + dev->reg_offsets[reg_index]); >> +} > >have you looked at regmap and if that can accomplish the same goal as = what this shadow stuff is doing? > No. Will look into it. >> + >> +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_d= ata *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) > >This should probably be something like __msm_spm_dump_regs() Ok. > >> +{ >> + 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 i= nt 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_level); >> + goto set_vdd_bail; >> + } >> + >> + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) >> + pr_info("%s: done, remaining timeout %u us\n", >> + __func__, timeout_us); >> + >> + return 0; >> + >> +set_vdd_bail: >> + pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n", >> + __func__, vlevel, timeout_us, new_level); >> + return -EIO; >> +} >> + >> +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *de= v, >> + 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(); >> + >> + 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_dr= iver.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 reserv= ed. >> + * >> + * This program is free software; you can redistribute it and/or mo= dify >> + * it under the terms of the GNU General Public License version 2 a= nd >> + * 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 reserv= ed. >> + * >> + * This program is free software; you can redistribute it and/or mo= dify >> + * it under the terms of the GNU General Public License version 2 a= nd >> + * only version 2 as published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + */ >> + >> +#ifndef __QCOM_SPM_H >> +#define __QCOM_SPM_H >> + >> +enum { >> + MSM_SPM_MODE_DISABLED, >> + MSM_SPM_MODE_CLOCK_GATING, >> + MSM_SPM_MODE_RETENTION, >> + MSM_SPM_MODE_GDHS, >> + MSM_SPM_MODE_POWER_COLLAPSE, >> + MSM_SPM_MODE_NR >> +}; >> + >> +struct msm_spm_device; >> + >> +#if defined(CONFIG_QCOM_PM) > >Where is CONFIG_QCOM_PM defined? Wondering if we should have a CONFIG= _QCOM_SPM and it can depend on any future =E2=80=98QCOM_PM=E2=80=99 in = the Kconfig. In the following patch that introduces SoC interface layer (msm-pm.c). I will restructure the patches and blend in QCOM_PM as part of this driver. So this driver may be compiled in. But it may not be of much us= e until msm-pm.c comes along. > >> +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); >> +int msm_spm_probe_done(void); >> +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel); >> +unsigned int msm_spm_get_vdd(unsigned int cpu); >> +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, = int cpu); >> +struct msm_spm_device *msm_spm_get_device_by_name(const char *name)= ; >> +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, >> + unsigned int mode, bool notify_rpm); >> +int msm_spm_device_init(void); >> +bool msm_spm_is_mode_avail(unsigned int mode); >> +void msm_spm_dump_regs(unsigned int cpu); >> +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt); >> +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode); >> +#else /* defined(CONFIG_QCOM_PM) */ >> +static inline int msm_spm_set_low_power_mode(unsigned int mode, boo= l 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 vl= evel) >> +{ 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_devi= ce *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 phas= e_cnt) >> +{ return -ENOSYS; } >> +static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) >> +{ return -ENOSYS; } >> +#endif /* defined (CONFIG_QCOM_PM) */ >> + >> +#endif /* __QCOM_SPM_H */ >> -- >> 1.9.1 >> >> -- >> To unsubscribe from this list: send the line "unsubscribe linux-arm-= msm" in >> the body of a message to majordomo@vger.kernel.org >> More majordomo info at http://vger.kernel.org/majordomo-info.html > >--=20 >Employee of Qualcomm Innovation Center, Inc. >Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hos= ted by The Linux Foundation >