On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@xxxxxxxxxx> wrote: > Qualcomm chipsets use an separate h/w block to control the logic around > the processor cores (cpu and L2). The SPM h/w block regulates power to > the cores and controls the power when the core enter low power modes. > > Each core has its own instance of SPM. The SPM has the following key > functions > - Configure the h/w dependencies when entering low power modes > - Wait for interrupt and wake up on interrupt > - Ensure the dependencies are ready before bringing the core out > of sleep > - Regulating voltage to the core, interfacing with the PMIC. > - Optimize power based on runtime recommendations. > > The driver identifies and configures the SPMs, by reading the nodes and > the register values from the devicetree. The SPMs need to be configured > to allow the processor to be idled in a low power state. > > Signed-off-by: Praveen Chidamabram <pchidamb@xxxxxxxxxxxxxx> > Signed-off-by: Murali Nalajala <mnalajal@xxxxxxxxxxxxxx> > Signed-off-by: Lina Iyer <lina.iyer@xxxxxxxxxx> > --- > .../devicetree/bindings/arm/msm/spm-v2.txt | 62 ++ > drivers/soc/qcom/Makefile | 2 + > drivers/soc/qcom/spm-devices.c | 703 +++++++++++++++++++++ > drivers/soc/qcom/spm.c | 482 ++++++++++++++ > drivers/soc/qcom/spm_driver.h | 116 ++++ > include/soc/qcom/spm.h | 70 ++ > 6 files changed, 1435 insertions(+) > create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt > create mode 100644 drivers/soc/qcom/spm-devices.c > create mode 100644 drivers/soc/qcom/spm.c > create mode 100644 drivers/soc/qcom/spm_driver.h > create mode 100644 include/soc/qcom/spm.h > > diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt > new file mode 100644 > index 0000000..3130f4b > --- /dev/null > +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt > @@ -0,0 +1,62 @@ > +* MSM Subsystem Power Manager (spm-v2) > + > +S4 generation of MSMs have SPM hardware blocks to control the Application > +Processor Sub-System power. These SPM blocks run individual state machine > +to determine what the core (L2 or Krait/Scorpion) would do when the WFI > +instruction is executed by the core. > + > +The devicetree representation of the SPM block should be: > + > +Required properties > + > +- compatible: Could be one of - > + "qcom,spm-v2.1" > + "qcom,spm-v3.0" > +- reg: The physical address and the size of the SPM's memory mapped registers > +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets > + that dont support CPU phandles the driver would support qcom,core-id. > + This field is required on only for SPMs that control the CPU. > +- qcom,saw2-cfg: SAW2 configuration register > +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM > + sequence > +- qcom,saw2-spm-ctl: The SPM control register > +- qcom,name: The name with which a SPM device is identified by the power > + management code. Still not sure about this, wondering why we can’t use the node name. > + > +Optional properties > + > +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS > + (Fast Transient Switch) index to send the PMIC data to > +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing > + voltage > +- qcom,phase-port: The PVC port used for changing the number of phases > +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes > +- qcom,saw2-spm-cmd-wfi: The WFI command sequence > +- qcom,saw2-spm-cmd-ret: The Retention command sequence > +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence > +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS > + proc won't inform the RPM. > +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may > + turn off other SoC components. > +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command > + sequence. This sequence will retain the memory but turn off the logic. > +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device > + can control. > +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to > + change after sending the voltage command to the PMIC. > +- > +Example: > + qcom,spm@f9089000 { > + compatible = "qcom,spm-v2"; > + #address-cells = <1>; > + #size-cells = <1>; > + reg = <0xf9089000 0x1000>; > + qcom,cpu = <&CPU0>; > + qcom,saw2-cfg = <0x1>; > + qcom,saw2-spm-dly= <0x20000400>; > + qcom,saw2-spm-ctl = <0x1>; > + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; > + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 > + a0 b0 03 68 70 3b 92 a0 b0 > + 82 2b 50 10 30 02 22 30 0f]; > + }; > diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile > index 70d52ed..d7ae93b 100644 > --- a/drivers/soc/qcom/Makefile > +++ b/drivers/soc/qcom/Makefile > @@ -1,3 +1,5 @@ > obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o > +obj-$(CONFIG_QCOM_PM) += spm-devices.o spm.o > + > CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) > obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o > diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c > new file mode 100644 > index 0000000..567e9f9 > --- /dev/null > +++ b/drivers/soc/qcom/spm-devices.c > @@ -0,0 +1,703 @@ > +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/delay.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/slab.h> > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/err.h> > +#include <linux/platform_device.h> > +#include <linux/err.h> > + > +#include <soc/qcom/spm.h> > + > +#include "spm_driver.h" > + > +#define VDD_DEFAULT 0xDEADF00D > + > +struct msm_spm_power_modes { > + uint32_t mode; > + bool notify_rpm; > + uint32_t start_addr; > +}; > + > +struct msm_spm_device { > + struct list_head list; > + bool initialized; > + const char *name; > + struct msm_spm_driver_data reg_data; > + struct msm_spm_power_modes *modes; > + uint32_t num_modes; > + uint32_t cpu_vdd; > + struct cpumask mask; > + void __iomem *q2s_reg; > +}; > + > +struct msm_spm_vdd_info { > + struct msm_spm_device *vctl_dev; > + uint32_t vlevel; > + int err; > +}; > + > +static LIST_HEAD(spm_list); > +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device); > +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device); > + > +static void msm_spm_smp_set_vdd(void *data) > +{ > + struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data; > + struct msm_spm_device *dev = info->vctl_dev; > + > + dev->cpu_vdd = info->vlevel; > + info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel); > +} > + > +/** > + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2 > + * probe. > + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER. > + * if probe failed, then return the err number for that failure. > + */ > +int msm_spm_probe_done(void) > +{ > + struct msm_spm_device *dev; > + int cpu; > + int ret = 0; > + > + for_each_possible_cpu(cpu) { > + dev = per_cpu(cpu_vctl_device, cpu); > + if (!dev) > + return -EPROBE_DEFER; > + > + ret = IS_ERR(dev); > + if (ret) > + return ret; > + } > + > + return 0; > +} > +EXPORT_SYMBOL(msm_spm_probe_done); > + > +void msm_spm_dump_regs(unsigned int cpu) > +{ > + dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu); > +} Where is this used? > + > +/** > + * msm_spm_set_vdd(): Set core voltage > + * @cpu: core id > + * @vlevel: Encoded PMIC data. > + * > + * Return: 0 on success or -(ERRNO) on failure. > + */ > +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) > +{ > + struct msm_spm_vdd_info info; > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + int ret; > + > + if (!dev) > + return -EPROBE_DEFER; > + > + ret = IS_ERR(dev); > + if (ret) > + return ret; > + > + info.vctl_dev = dev; > + info.vlevel = vlevel; > + > + ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info, > + true); > + if (ret) > + return ret; > + > + return info.err; > +} > +EXPORT_SYMBOL(msm_spm_set_vdd); > + > +/** > + * msm_spm_get_vdd(): Get core voltage > + * @cpu: core id > + * @return: Returns encoded PMIC data. > + */ > +unsigned int msm_spm_get_vdd(unsigned int cpu) > +{ > + int ret; > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + if (!dev) > + return -EPROBE_DEFER; > + > + ret = IS_ERR(dev); > + if (ret) > + return ret; > + > + return dev->cpu_vdd; > +} > +EXPORT_SYMBOL(msm_spm_get_vdd); > + > +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode) > +{ > + uint32_t spm_legacy_mode = 0; > + uint32_t qchannel_ignore = 0; > + uint32_t val = 0; > + > + if (!dev->q2s_reg) > + return; > + > + switch (mode) { > + case MSM_SPM_MODE_DISABLED: > + case MSM_SPM_MODE_CLOCK_GATING: > + qchannel_ignore = 1; > + spm_legacy_mode = 0; > + break; > + case MSM_SPM_MODE_RETENTION: > + qchannel_ignore = 0; > + spm_legacy_mode = 0; > + break; > + case MSM_SPM_MODE_GDHS: > + case MSM_SPM_MODE_POWER_COLLAPSE: > + qchannel_ignore = 0; > + spm_legacy_mode = 1; > + break; > + default: > + break; > + } > + > + val = spm_legacy_mode << 2 | qchannel_ignore << 1; > + __raw_writel(val, dev->q2s_reg); > + mb(); > +} > + > +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm) > +{ > + uint32_t i; > + uint32_t start_addr = 0; > + int ret = -EINVAL; > + bool pc_mode = false; > + > + if (!dev->initialized) > + return -ENXIO; > + > + if ((mode == MSM_SPM_MODE_POWER_COLLAPSE) > + || (mode == MSM_SPM_MODE_GDHS)) > + pc_mode = true; > + > + if (mode == MSM_SPM_MODE_DISABLED) { > + ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false); > + } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) { > + for (i = 0; i < dev->num_modes; i++) { > + if ((dev->modes[i].mode == mode) && > + (dev->modes[i].notify_rpm == notify_rpm)) { > + start_addr = dev->modes[i].start_addr; > + break; > + } > + } > + ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, > + start_addr, pc_mode); > + } > + > + msm_spm_config_q2s(dev, mode); > + > + return ret; > +} > + > +static int msm_spm_dev_init(struct msm_spm_device *dev, > + struct msm_spm_platform_data *data) > +{ > + int i, ret = -ENOMEM; > + uint32_t offset = 0; > + > + dev->cpu_vdd = VDD_DEFAULT; > + dev->num_modes = data->num_modes; > + dev->modes = kmalloc( > + sizeof(struct msm_spm_power_modes) * dev->num_modes, > + GFP_KERNEL); > + > + if (!dev->modes) > + goto spm_failed_malloc; > + > + dev->reg_data.major = data->major; > + dev->reg_data.minor = data->minor; > + ret = msm_spm_drv_init(&dev->reg_data, data); > + > + if (ret) > + goto spm_failed_init; > + > + for (i = 0; i < dev->num_modes; i++) { > + > + /* Default offset is 0 and gets updated as we write more > + * sequences into SPM > + */ > + dev->modes[i].start_addr = offset; > + ret = msm_spm_drv_write_seq_data(&dev->reg_data, > + data->modes[i].cmd, &offset); > + if (ret < 0) > + goto spm_failed_init; > + > + dev->modes[i].mode = data->modes[i].mode; > + dev->modes[i].notify_rpm = data->modes[i].notify_rpm; > + } > + msm_spm_drv_reinit(&dev->reg_data); > + dev->initialized = true; > + return 0; > + > +spm_failed_init: > + kfree(dev->modes); > +spm_failed_malloc: > + return ret; > +} > + > +/** > + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core > + * @base: The SAW VCTL register which would set the voltage up. > + * @val: The value to be set on the rail > + * @cpu: The cpu for this with rail is being powered on > + */ > +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu) > +{ > + uint32_t timeout = 2000; /* delay for voltage to settle on the core */ > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + /* > + * If clock drivers have already set up the voltage, > + * do not overwrite that value. > + */ > + if (dev && (dev->cpu_vdd != VDD_DEFAULT)) > + return 0; > + > + /* Set the CPU supply regulator voltage */ > + val = (val & 0xFF); > + writel_relaxed(val, base); > + mb(); > + udelay(timeout); > + > + /* Enable the CPU supply regulator*/ > + val = 0x30080; > + writel_relaxed(val, base); > + mb(); > + udelay(timeout); > + > + return 0; > +} > +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail); > + > +void msm_spm_reinit(void) > +{ > + unsigned int cpu; > + > + for_each_possible_cpu(cpu) > + msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu)); > +} > +EXPORT_SYMBOL(msm_spm_reinit); > + > +/* > + * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu > + * It should only be used to decide a mode before lpm driver is probed. > + * @mode: SPM LPM mode to be selected > + */ > +bool msm_spm_is_mode_avail(unsigned int mode) > +{ > + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); > + int i; > + > + for (i = 0; i < dev->num_modes; i++) { > + if (dev->modes[i].mode == mode) > + return true; > + } > + > + return false; > +} > + > +/** > + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode > + * @mode: SPM LPM mode to enter > + * @notify_rpm: Notify RPM in this mode > + */ > +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) > +{ > + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); > + > + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); > +} > +EXPORT_SYMBOL(msm_spm_set_low_power_mode); > + > +/** > + * msm_spm_init(): Board initalization function > + * @data: platform specific SPM register configuration data > + * @nr_devs: Number of SPM devices being initialized > + */ > +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) > +{ > + unsigned int cpu; > + int ret = 0; > + > + BUG_ON((nr_devs < num_possible_cpus()) || !data); > + > + for_each_possible_cpu(cpu) { > + struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu); > + > + ret = msm_spm_dev_init(dev, &data[cpu]); > + if (ret < 0) { > + pr_warn("%s():failed CPU:%u ret:%d\n", __func__, > + cpu, ret); > + break; > + } > + } > + > + return ret; > +} > + > +struct msm_spm_device *msm_spm_get_device_by_name(const char *name) > +{ > + struct list_head *list; > + > + list_for_each(list, &spm_list) { > + struct msm_spm_device *dev > + = list_entry(list, typeof(*dev), list); > + if (dev->name && !strcmp(dev->name, name)) > + return dev; > + } > + return ERR_PTR(-ENODEV); > +} > + > +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm) > +{ > + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); > +} > +#ifdef CONFIG_MSM_L2_SPM > + > +/** > + * msm_spm_apcs_set_phase(): Set number of SMPS phases. > + * @cpu: cpu which is requesting the change in number of phases. > + * @phase_cnt: Number of phases to be set active > + */ > +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) > +{ > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + if (!dev) > + return -ENXIO; > + > + return msm_spm_drv_set_pmic_data(&dev->reg_data, > + MSM_SPM_PMIC_PHASE_PORT, phase_cnt); > +} > +EXPORT_SYMBOL(msm_spm_apcs_set_phase); > + > +/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power > + * when the cores are in low power modes > + * @cpu: cpu that is entering low power mode. > + * @mode: The mode configuration for FTS > + */ > +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) > +{ > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + if (!dev) > + return -ENXIO; > + > + return msm_spm_drv_set_pmic_data(&dev->reg_data, > + MSM_SPM_PMIC_PFM_PORT, mode); > +} > +EXPORT_SYMBOL(msm_spm_enable_fts_lpm); > + > +#endif > + > +static int get_cpu_id(struct device_node *node) > +{ > + struct device_node *cpu_node; > + u32 cpu; > + int ret = -EINVAL; > + char *key = "qcom,cpu"; > + > + cpu_node = of_parse_phandle(node, key, 0); > + if (cpu_node) { > + for_each_possible_cpu(cpu) { > + if (of_get_cpu_node(cpu, NULL) == cpu_node) > + return cpu; > + } > + } > + return ret; > +} > + > +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev) > +{ > + struct msm_spm_device *dev = NULL; > + const char *val = NULL; > + char *key = "qcom,name"; > + int cpu = get_cpu_id(pdev->dev.of_node); > + > + if ((cpu >= 0) && cpu < num_possible_cpus()) > + dev = &per_cpu(msm_cpu_spm_device, cpu); > + else if ((cpu == 0xffff) || (cpu < 0)) > + dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device), > + GFP_KERNEL); > + > + if (!dev) > + return NULL; > + > + if (of_property_read_string(pdev->dev.of_node, key, &val)) { > + pr_err("%s(): Cannot find a required node key:%s\n", > + __func__, key); > + return NULL; > + } > + dev->name = val; > + list_add(&dev->list, &spm_list); > + > + return dev; > +} > + > +static void get_cpumask(struct device_node *node, struct cpumask *mask) > +{ > + unsigned long vctl_mask = 0; > + unsigned c = 0; > + int idx = 0; > + struct device_node *cpu_node = NULL; > + int ret = 0; > + char *key = "qcom,cpu-vctl-list"; > + bool found = false; > + > + cpu_node = of_parse_phandle(node, key, idx++); > + while (cpu_node) { > + found = true; > + for_each_possible_cpu(c) { > + if (of_get_cpu_node(c, NULL) == cpu_node) > + cpumask_set_cpu(c, mask); > + } > + cpu_node = of_parse_phandle(node, key, idx++); > + }; > + > + if (found) > + return; > + > + key = "qcom,cpu-vctl-mask"; > + ret = of_property_read_u32(node, key, (u32 *) &vctl_mask); > + if (!ret) { > + for_each_set_bit(c, &vctl_mask, num_possible_cpus()) { > + cpumask_set_cpu(c, mask); > + } > + } kill this code if we really dropped the ‘qcom,cpu-vctl-mask’ DT support. > +} > + > +static int msm_spm_dev_probe(struct platform_device *pdev) > +{ > + int ret = 0; > + int cpu = 0; > + int i = 0; > + struct device_node *node = pdev->dev.of_node; > + struct msm_spm_platform_data spm_data; > + char *key = NULL; > + uint32_t val = 0; > + struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR]; > + int len = 0; > + struct msm_spm_device *dev = NULL; > + struct resource *res = NULL; > + uint32_t mode_count = 0; > + > + struct spm_of { > + char *key; > + uint32_t id; > + }; > + > + struct spm_of spm_of_data[] = { > + {"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG}, > + {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY}, > + {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL}, > + {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0}, > + {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1}, > + {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2}, > + {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3}, > + {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4}, > + {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5}, > + {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6}, > + {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7}, > + }; > + > + struct mode_of { > + char *key; > + uint32_t id; > + uint32_t notify_rpm; > + }; > + > + struct mode_of mode_of_data[] = { > + {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0}, > + {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0}, > + {"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1}, > + {"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0}, > + {"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1}, > + }; > + > + dev = msm_spm_get_device(pdev); > + if (!dev) { > + ret = -ENOMEM; > + goto fail; > + } > + get_cpumask(node, &dev->mask); > + > + memset(&spm_data, 0, sizeof(struct msm_spm_platform_data)); > + memset(&modes, 0, > + (MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry)); > + > + if (of_device_is_compatible(node, "qcom,spm-v2.1")) { > + spm_data.major = 2; > + spm_data.minor = 1; > + } else if (of_device_is_compatible(node, "qcom,spm-v3.0")) { > + spm_data.major = 3; > + spm_data.minor = 0; > + } > + > + key = "qcom,vctl-timeout-us"; > + ret = of_property_read_u32(node, key, &val); > + if (!ret) > + spm_data.vctl_timeout_us = val; > + > + /* SAW start address */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + ret = -EFAULT; > + goto fail; > + } > + > + spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start, > + resource_size(res)); > + if (!spm_data.reg_base_addr) { > + ret = -ENOMEM; > + goto fail; > + } > + > + spm_data.vctl_port = -1; > + spm_data.phase_port = -1; > + spm_data.pfm_port = -1; > + > + key = "qcom,vctl-port"; > + of_property_read_u32(node, key, &spm_data.vctl_port); > + > + key = "qcom,phase-port"; > + of_property_read_u32(node, key, &spm_data.phase_port); > + > + key = "qcom,pfm-port"; > + of_property_read_u32(node, key, &spm_data.pfm_port); > + > + /* Q2S (QChannel-2-SPM) register */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); > + if (res) { > + dev->q2s_reg = devm_ioremap(&pdev->dev, res->start, > + resource_size(res)); > + if (!dev->q2s_reg) { > + pr_err("%s(): Unable to iomap Q2S register\n", > + __func__); > + ret = -EADDRNOTAVAIL; > + goto fail; > + } > + } > + /* > + * At system boot, cpus and or clusters can remain in reset. CCI SPM > + * will not be triggered unless SPM_LEGACY_MODE bit is set for the > + * cluster in reset. Initialize q2s registers and set the > + * SPM_LEGACY_MODE bit. > + */ > + msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE); > + > + for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) { > + ret = of_property_read_u32(node, spm_of_data[i].key, &val); > + if (ret) > + continue; > + spm_data.reg_init_values[spm_of_data[i].id] = val; > + } > + > + for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) { > + key = mode_of_data[i].key; > + modes[mode_count].cmd = > + (uint8_t *)of_get_property(node, key, &len); > + if (!modes[mode_count].cmd) > + continue; > + modes[mode_count].mode = mode_of_data[i].id; > + modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm; > + pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__, > + dev->name, key, modes[mode_count].mode, > + modes[mode_count].notify_rpm); > + mode_count++; > + } > + > + spm_data.modes = modes; > + spm_data.num_modes = mode_count; > + > + ret = msm_spm_dev_init(dev, &spm_data); > + if (ret) > + goto fail; > + > + platform_set_drvdata(pdev, dev); > + > + for_each_cpu(cpu, &dev->mask) > + per_cpu(cpu_vctl_device, cpu) = dev; > + > + return ret; > + > +fail: > + cpu = get_cpu_id(pdev->dev.of_node); > + if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) { > + for_each_cpu(cpu, &dev->mask) > + per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret); > + } > + > + pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret); > + > + return ret; > +} > + > +static int msm_spm_dev_remove(struct platform_device *pdev) > +{ > + struct msm_spm_device *dev = platform_get_drvdata(pdev); > + > + list_del(&dev->list); > + > + return 0; > +} > + > +static struct of_device_id msm_spm_match_table[] = { > + {.compatible = "qcom,spm-v2.1"}, > + {.compatible = "qcom,spm-v3.0"}, > + {}, > +}; > + > +static struct platform_driver msm_spm_device_driver = { > + .probe = msm_spm_dev_probe, > + .remove = msm_spm_dev_remove, > + .driver = { > + .name = "spm-v2", > + .owner = THIS_MODULE, > + .of_match_table = msm_spm_match_table, > + }, > +}; > + > +/** > + * msm_spm_device_init(): Device tree initialization function > + */ > +int __init msm_spm_device_init(void) > +{ > + static bool registered; > + > + if (registered) > + return 0; > + > + registered = true; > + > + return platform_driver_register(&msm_spm_device_driver); > +} > +device_initcall(msm_spm_device_init); > diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c > new file mode 100644 > index 0000000..7dbdb64 > --- /dev/null > +++ b/drivers/soc/qcom/spm.c > @@ -0,0 +1,482 @@ > +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/delay.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/slab.h> > + > +#include "spm_driver.h" > + > +#define MSM_SPM_PMIC_STATE_IDLE 0 > + > +enum { > + MSM_SPM_DEBUG_SHADOW = 1U << 0, > + MSM_SPM_DEBUG_VCTL = 1U << 1, > +}; > + > +static int msm_spm_debug_mask; > +module_param_named( > + debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP > +); > + > +struct saw2_data { > + const char *ver_name; > + uint32_t major; > + uint32_t minor; > + uint32_t *spm_reg_offset_ptr; > +}; > + > +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = { > + [MSM_SPM_REG_SAW2_SECURE] = 0x00, > + [MSM_SPM_REG_SAW2_ID] = 0x04, > + [MSM_SPM_REG_SAW2_CFG] = 0x08, > + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, > + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, > + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, > + [MSM_SPM_REG_SAW2_RST] = 0x18, > + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, > + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, > + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, > + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, > + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, > + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, > + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, > + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, > + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, > + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, > + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, > + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, > + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, > + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, > + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, > + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, > + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, > +}; > + > +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = { > + [MSM_SPM_REG_SAW2_SECURE] = 0x00, > + [MSM_SPM_REG_SAW2_ID] = 0x04, > + [MSM_SPM_REG_SAW2_CFG] = 0x08, > + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, > + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, > + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, > + [MSM_SPM_REG_SAW2_RST] = 0x18, > + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, > + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, > + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, > + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, > + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, > + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, > + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, > + [MSM_SPM_REG_SAW2_STS2] = 0x38, > + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, > + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, > + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, > + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, > + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, > + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, > + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, > + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, > + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x400, > + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, > +}; I don’t see what having these arrays provides as the only differences are that v3.0 has MSM_SPM_REG_SAW2_STS2 and the offset of MSM_SPM_REG_SAW2_SEQ_ENTRY. If so we can remove all this extra code and just add a simple check in msm_spm_drv_flush_seq_entry that looks at the compatible and picks the proper offset when updating MSM_SPM_REG_SAW2_SEQ_ENTRY. > + > +static struct saw2_data saw2_info[] = { > + [0] = { > + "SAW2_v2.1", > + 2, > + 1, > + msm_spm_reg_offsets_saw2_v2_1, > + }, > + [1] = { > + "SAW2_v3.0", > + 3, > + 0, > + msm_spm_reg_offsets_saw2_v3_0, > + }, > +}; > + > +static uint32_t num_pmic_data; > + > +static inline uint32_t msm_spm_drv_get_num_spm_entry( > + struct msm_spm_driver_data *dev) > +{ > + return 32; > +} > + > +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, > + unsigned int reg_index) > +{ > + __raw_writel(dev->reg_shadow[reg_index], > + dev->reg_base_addr + dev->reg_offsets[reg_index]); > +} have you looked at regmap and if that can accomplish the same goal as what this shadow stuff is doing? > + > +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, > + unsigned int reg_index) > +{ > + dev->reg_shadow[reg_index] = > + __raw_readl(dev->reg_base_addr + > + dev->reg_offsets[reg_index]); > +} > + > +static inline void msm_spm_drv_set_start_addr( > + struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode) > +{ > + addr &= 0x7F; > + addr <<= 4; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; > + > + if (dev->major != 0x3) > + return; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF; > + if (pc_mode) > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000; > +} > + > +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; > +} > + > +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, > + uint32_t vlevel) > +{ > + unsigned int pmic_data = 0; > + > + /** > + * VCTL_PORT has to be 0, for PMIC_STS register to be updated. > + * Ensure that vctl_port is always set to 0. > + */ > + WARN_ON(dev->vctl_port); > + > + pmic_data |= vlevel; > + pmic_data |= (dev->vctl_port & 0x7) << 16; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data; > + > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3); > +} > + > +static inline uint32_t msm_spm_drv_get_num_pmic_data( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); > + mb(); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7; > +} > + > +static inline uint32_t msm_spm_drv_get_sts_pmic_state( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & > + 0x03; > +} > + > +uint32_t msm_spm_drv_get_sts_curr_pmic_data( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); > + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; > +} > + > +inline int msm_spm_drv_set_spm_enable( > + struct msm_spm_driver_data *dev, bool enable) > +{ > + uint32_t value = enable ? 0x01 : 0x00; > + > + if (!dev) > + return -EINVAL; > + > + if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; > + > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); > + wmb(); > + } > + return 0; > +} > +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev) > +{ > + int i; > + int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); > + > + if (!dev) { > + __WARN(); > + return; > + } > + > + for (i = 0; i < num_spm_entry; i++) { > + __raw_writel(dev->reg_seq_entry_shadow[i], > + dev->reg_base_addr > + + dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] > + + 4 * i); > + } > + mb(); > +} > + > +void dump_regs(struct msm_spm_driver_data *dev, int cpu) This should probably be something like __msm_spm_dump_regs() > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); > + mb(); > + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu, > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]); > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); > + mb(); > + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu, > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]); > +} > + > +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, > + uint8_t *cmd, uint32_t *offset) > +{ > + uint32_t cmd_w; > + uint32_t offset_w = *offset / 4; > + uint8_t last_cmd; > + > + if (!cmd) > + return -EINVAL; > + > + while (1) { > + int i; > + > + cmd_w = 0; > + last_cmd = 0; > + cmd_w = dev->reg_seq_entry_shadow[offset_w]; > + > + for (i = (*offset % 4); i < 4; i++) { > + last_cmd = *(cmd++); > + cmd_w |= last_cmd << (i * 8); > + (*offset)++; > + if (last_cmd == 0x0f) > + break; > + } > + > + dev->reg_seq_entry_shadow[offset_w++] = cmd_w; > + if (last_cmd == 0x0f) > + break; > + } > + > + return 0; > +} > + > +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, > + uint32_t addr, bool pc_mode) > +{ > + > + if (!dev) > + return -EINVAL; > + > + msm_spm_drv_set_start_addr(dev, addr, pc_mode); > + > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); > + wmb(); > + > + if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { > + int i; > + > + for (i = 0; i < MSM_SPM_REG_NR; i++) > + pr_info("%s: reg %02x = 0x%08x\n", __func__, > + dev->reg_offsets[i], dev->reg_shadow[i]); > + } > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); > + > + return 0; > +} > + > +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel) > +{ > + uint32_t timeout_us, new_level; > + > + if (!dev) > + return -EINVAL; > + > + if (!msm_spm_pmic_arb_present(dev)) > + return -ENOSYS; > + > + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) > + pr_info("%s: requesting vlevel %#x\n", __func__, vlevel); > + > + /* Kick the state machine back to idle */ > + dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1; > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST); > + > + msm_spm_drv_set_vctl2(dev, vlevel); > + > + timeout_us = dev->vctl_timeout_us; > + /* Confirm the voltage we set was what hardware sent */ > + do { > + new_level = msm_spm_drv_get_sts_curr_pmic_data(dev); > + if (new_level == vlevel) > + break; > + udelay(1); > + } while (--timeout_us); > + if (!timeout_us) { > + pr_info("Wrong level %#x\n", new_level); > + goto set_vdd_bail; > + } > + > + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) > + pr_info("%s: done, remaining timeout %u us\n", > + __func__, timeout_us); > + > + return 0; > + > +set_vdd_bail: > + pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n", > + __func__, vlevel, timeout_us, new_level); > + return -EIO; > +} > + > +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev, > + enum msm_spm_pmic_port port) > +{ > + int index = -1; > + > + switch (port) { > + case MSM_SPM_PMIC_VCTL_PORT: > + index = dev->vctl_port; > + break; > + case MSM_SPM_PMIC_PHASE_PORT: > + index = dev->phase_port; > + break; > + case MSM_SPM_PMIC_PFM_PORT: > + index = dev->pfm_port; > + break; > + default: > + break; > + } > + > + return index; > +} > + > +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, > + enum msm_spm_pmic_port port, unsigned int data) > +{ > + unsigned int pmic_data = 0; > + unsigned int timeout_us = 0; > + int index = 0; > + > + if (!msm_spm_pmic_arb_present(dev)) > + return -ENOSYS; > + > + index = msm_spm_drv_get_pmic_port(dev, port); > + if (index < 0) > + return -ENODEV; > + > + pmic_data |= data & 0xFF; > + pmic_data |= (index & 0x7) << 16; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); > + mb(); > + > + timeout_us = dev->vctl_timeout_us; > + /** > + * Confirm the pmic data set was what hardware sent by > + * checking the PMIC FSM state. > + * We cannot use the sts_pmic_data and check it against > + * the value like we do fot set_vdd, since the PMIC_STS > + * is only updated for SAW_VCTL sent with port index 0. > + */ > + do { > + if (msm_spm_drv_get_sts_pmic_state(dev) == > + MSM_SPM_PMIC_STATE_IDLE) > + break; > + udelay(1); > + } while (--timeout_us); > + > + if (!timeout_us) { > + pr_err("%s: failed, remaining timeout %u us, data %d\n", > + __func__, timeout_us, data); > + return -EIO; > + } > + > + return 0; > +} > + > +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev) > +{ > + int i; > + > + msm_spm_drv_flush_seq_entry(dev); > + for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++) > + msm_spm_drv_flush_shadow(dev, i); > + > + mb(); > + > + for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++) > + msm_spm_drv_load_shadow(dev, i); > +} > + > +int msm_spm_drv_init(struct msm_spm_driver_data *dev, > + struct msm_spm_platform_data *data) > +{ > + int i; > + int num_spm_entry; > + bool found = false; > + > + BUG_ON(!dev || !data); > + > + dev->vctl_port = data->vctl_port; > + dev->phase_port = data->phase_port; > + dev->pfm_port = data->pfm_port; > + dev->reg_base_addr = data->reg_base_addr; > + memcpy(dev->reg_shadow, data->reg_init_values, > + sizeof(data->reg_init_values)); > + > + dev->vctl_timeout_us = data->vctl_timeout_us; > + > + for (i = 0; i < ARRAY_SIZE(saw2_info); i++) > + if (dev->major == saw2_info[i].major && > + dev->minor == saw2_info[i].minor) { > + pr_debug("%s: Version found\n", > + saw2_info[i].ver_name); > + dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr; > + found = true; > + break; > + } > + > + if (!found) { > + pr_err("%s: No SAW2 version found\n", __func__); > + BUG_ON(!found); > + } > + > + if (!num_pmic_data) > + num_pmic_data = msm_spm_drv_get_num_pmic_data(dev); > + > + num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); > + > + dev->reg_seq_entry_shadow = > + kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry, > + GFP_KERNEL); > + > + if (!dev->reg_seq_entry_shadow) > + return -ENOMEM; > + > + return 0; > +} > diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h > new file mode 100644 > index 0000000..b306520 > --- /dev/null > +++ b/drivers/soc/qcom/spm_driver.h > @@ -0,0 +1,116 @@ > +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > +#ifndef __QCOM_SPM_DRIVER_H > +#define __QCOM_SPM_DRIVER_H > + > +#include <soc/qcom/spm.h> > + > +enum { > + MSM_SPM_REG_SAW2_CFG, > + MSM_SPM_REG_SAW2_AVS_CTL, > + MSM_SPM_REG_SAW2_AVS_HYSTERESIS, > + MSM_SPM_REG_SAW2_SPM_CTL, > + MSM_SPM_REG_SAW2_PMIC_DLY, > + MSM_SPM_REG_SAW2_AVS_LIMIT, > + MSM_SPM_REG_SAW2_AVS_DLY, > + MSM_SPM_REG_SAW2_SPM_DLY, > + MSM_SPM_REG_SAW2_PMIC_DATA_0, > + MSM_SPM_REG_SAW2_PMIC_DATA_1, > + MSM_SPM_REG_SAW2_PMIC_DATA_2, > + MSM_SPM_REG_SAW2_PMIC_DATA_3, > + MSM_SPM_REG_SAW2_PMIC_DATA_4, > + MSM_SPM_REG_SAW2_PMIC_DATA_5, > + MSM_SPM_REG_SAW2_PMIC_DATA_6, > + MSM_SPM_REG_SAW2_PMIC_DATA_7, > + MSM_SPM_REG_SAW2_RST, > + > + MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST, > + > + MSM_SPM_REG_SAW2_ID, > + MSM_SPM_REG_SAW2_SECURE, > + MSM_SPM_REG_SAW2_STS0, > + MSM_SPM_REG_SAW2_STS1, > + MSM_SPM_REG_SAW2_STS2, > + MSM_SPM_REG_SAW2_VCTL, > + MSM_SPM_REG_SAW2_SEQ_ENTRY, > + MSM_SPM_REG_SAW2_SPM_STS, > + MSM_SPM_REG_SAW2_AVS_STS, > + MSM_SPM_REG_SAW2_PMIC_STS, > + MSM_SPM_REG_SAW2_VERSION, > + > + MSM_SPM_REG_NR, > +}; > + > +struct msm_spm_seq_entry { > + uint32_t mode; > + uint8_t *cmd; > + bool notify_rpm; > +}; > + > +struct msm_spm_platform_data { > + void __iomem *reg_base_addr; > + uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE]; > + > + uint32_t major; > + uint32_t minor; > + uint32_t vctl_port; > + uint32_t phase_port; > + uint32_t pfm_port; > + > + uint8_t awake_vlevel; > + uint32_t vctl_timeout_us; > + > + uint32_t num_modes; > + struct msm_spm_seq_entry *modes; > +}; > + > +enum msm_spm_pmic_port { > + MSM_SPM_PMIC_VCTL_PORT, > + MSM_SPM_PMIC_PHASE_PORT, > + MSM_SPM_PMIC_PFM_PORT, > +}; > + > +struct msm_spm_driver_data { > + uint32_t major; > + uint32_t minor; > + uint32_t vctl_port; > + uint32_t phase_port; > + uint32_t pfm_port; > + void __iomem *reg_base_addr; > + uint32_t vctl_timeout_us; > + uint32_t reg_shadow[MSM_SPM_REG_NR]; > + uint32_t *reg_seq_entry_shadow; > + uint32_t *reg_offsets; > +}; > + > +int msm_spm_drv_init(struct msm_spm_driver_data *dev, > + struct msm_spm_platform_data *data); > +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev); > +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, > + uint32_t addr, bool pc_mode); > +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, > + unsigned int vlevel); > +void dump_regs(struct msm_spm_driver_data *dev, int cpu); > +uint32_t msm_spm_drv_get_sts_curr_pmic_data( > + struct msm_spm_driver_data *dev); > +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, > + uint8_t *cmd, uint32_t *offset); > +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev); > +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, > + bool enable); > +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, > + enum msm_spm_pmic_port port, unsigned int data); > + > +void msm_spm_reinit(void); > +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs); > + > +#endif /* __QCOM_SPM_DRIVER_H */ > diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h > new file mode 100644 > index 0000000..f39e0c4 > --- /dev/null > +++ b/include/soc/qcom/spm.h > @@ -0,0 +1,70 @@ > +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#ifndef __QCOM_SPM_H > +#define __QCOM_SPM_H > + > +enum { > + MSM_SPM_MODE_DISABLED, > + MSM_SPM_MODE_CLOCK_GATING, > + MSM_SPM_MODE_RETENTION, > + MSM_SPM_MODE_GDHS, > + MSM_SPM_MODE_POWER_COLLAPSE, > + MSM_SPM_MODE_NR > +}; > + > +struct msm_spm_device; > + > +#if defined(CONFIG_QCOM_PM) Where is CONFIG_QCOM_PM defined? Wondering if we should have a CONFIG_QCOM_SPM and it can depend on any future ‘QCOM_PM’ in the Kconfig. > +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); > +int msm_spm_probe_done(void); > +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel); > +unsigned int msm_spm_get_vdd(unsigned int cpu); > +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu); > +struct msm_spm_device *msm_spm_get_device_by_name(const char *name); > +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm); > +int msm_spm_device_init(void); > +bool msm_spm_is_mode_avail(unsigned int mode); > +void msm_spm_dump_regs(unsigned int cpu); > +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt); > +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode); > +#else /* defined(CONFIG_QCOM_PM) */ > +static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) > +{ return -ENOSYS; } > +static inline int msm_spm_probe_done(void) > +{ return -ENOSYS; } > +static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) > +{ return -ENOSYS; } > +static inline unsigned int msm_spm_get_vdd(unsigned int cpu) > +{ return 0; } > +static inline int msm_spm_turn_on_cpu_rail(void __iomem *base, > + unsigned int val, int cpu) > +{ return -ENOSYS; } > +static inline int msm_spm_device_init(void) > +{ return -ENOSYS; } > +static void msm_spm_dump_regs(unsigned int cpu) {} > +static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm) > +{ return -ENODEV; } > +static inline struct msm_spm_device *msm_spm_get_device_by_name( > + const char *name) > +{ return NULL; } > +static inline bool msm_spm_is_mode_avail(unsigned int mode) > +{ return false; } > +static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) > +{ return -ENOSYS; } > +static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) > +{ return -ENOSYS; } > +#endif /* defined (CONFIG_QCOM_PM) */ > + > +#endif /* __QCOM_SPM_H */ > -- > 1.9.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Employee of Qualcomm Innovation Center, Inc. Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html