Message ID | 1407872640-6732-4-git-send-email-lina.iyer@linaro.org |
---|---|
State | New |
Headers | show |
On 08/12/2014 09:43 PM, Lina Iyer wrote: > Qualcomm chipsets use an separate h/w block to control the logic around > the processor cores (cpu and L2). The SPM h/w block regulates power to > the cores and controls the power when the core enter low power modes. > > Each core has its own instance of SPM. The SPM has the following key > functions > - Configure the h/w dependencies when entering low power modes > - Wait for interrupt and wake up on interrupt > - Ensure the dependencies are ready before bringing the core out > of sleep > - Regulating voltage to the core, interfacing with the PMIC. > - Optimize power based on runtime recommendations. > > The driver identifies and configures the SPMs, by reading the nodes and > the register values from the devicetree. The SPMs need to be configured > to allow the processor to be idled in a low power state. I began to comment but I realize I have a lot of questions and comments for this patch and because of its size, it will be impossible to follow a discussion. This patch is really too big to review, please split it into smaller chunks. Thanks -- Daniel > Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org> > Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org> > Signed-off-by: Lina Iyer <lina.iyer@linaro.org> > --- > .../devicetree/bindings/arm/msm/spm-v2.txt | 62 ++ > drivers/soc/qcom/Makefile | 2 + > drivers/soc/qcom/spm-devices.c | 703 +++++++++++++++++++++ > drivers/soc/qcom/spm.c | 482 ++++++++++++++ > drivers/soc/qcom/spm_driver.h | 116 ++++ > include/soc/qcom/spm.h | 70 ++ > 6 files changed, 1435 insertions(+) > create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt > create mode 100644 drivers/soc/qcom/spm-devices.c > create mode 100644 drivers/soc/qcom/spm.c > create mode 100644 drivers/soc/qcom/spm_driver.h > create mode 100644 include/soc/qcom/spm.h > > diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt > new file mode 100644 > index 0000000..3130f4b > --- /dev/null > +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt > @@ -0,0 +1,62 @@ > +* MSM Subsystem Power Manager (spm-v2) > + > +S4 generation of MSMs have SPM hardware blocks to control the Application > +Processor Sub-System power. These SPM blocks run individual state machine > +to determine what the core (L2 or Krait/Scorpion) would do when the WFI > +instruction is executed by the core. > + > +The devicetree representation of the SPM block should be: > + > +Required properties > + > +- compatible: Could be one of - > + "qcom,spm-v2.1" > + "qcom,spm-v3.0" > +- reg: The physical address and the size of the SPM's memory mapped registers > +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets > + that dont support CPU phandles the driver would support qcom,core-id. > + This field is required on only for SPMs that control the CPU. > +- qcom,saw2-cfg: SAW2 configuration register > +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM > + sequence > +- qcom,saw2-spm-ctl: The SPM control register > +- qcom,name: The name with which a SPM device is identified by the power > + management code. > + > +Optional properties > + > +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS > + (Fast Transient Switch) index to send the PMIC data to > +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing > + voltage > +- qcom,phase-port: The PVC port used for changing the number of phases > +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes > +- qcom,saw2-spm-cmd-wfi: The WFI command sequence > +- qcom,saw2-spm-cmd-ret: The Retention command sequence > +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence > +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS > + proc won't inform the RPM. > +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may > + turn off other SoC components. > +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command > + sequence. This sequence will retain the memory but turn off the logic. > +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device > + can control. > +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to > + change after sending the voltage command to the PMIC. > +- > +Example: > + qcom,spm@f9089000 { > + compatible = "qcom,spm-v2"; > + #address-cells = <1>; > + #size-cells = <1>; > + reg = <0xf9089000 0x1000>; > + qcom,cpu = <&CPU0>; > + qcom,saw2-cfg = <0x1>; > + qcom,saw2-spm-dly= <0x20000400>; > + qcom,saw2-spm-ctl = <0x1>; > + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; > + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 > + a0 b0 03 68 70 3b 92 a0 b0 > + 82 2b 50 10 30 02 22 30 0f]; > + }; > diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile > index 70d52ed..d7ae93b 100644 > --- a/drivers/soc/qcom/Makefile > +++ b/drivers/soc/qcom/Makefile > @@ -1,3 +1,5 @@ > obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o > +obj-$(CONFIG_QCOM_PM) += spm-devices.o spm.o > + > CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) > obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o > diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c > new file mode 100644 > index 0000000..567e9f9 > --- /dev/null > +++ b/drivers/soc/qcom/spm-devices.c > @@ -0,0 +1,703 @@ > +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/delay.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/slab.h> > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/err.h> > +#include <linux/platform_device.h> > +#include <linux/err.h> > + > +#include <soc/qcom/spm.h> > + > +#include "spm_driver.h" > + > +#define VDD_DEFAULT 0xDEADF00D > + > +struct msm_spm_power_modes { > + uint32_t mode; > + bool notify_rpm; > + uint32_t start_addr; > +}; > + > +struct msm_spm_device { > + struct list_head list; > + bool initialized; > + const char *name; > + struct msm_spm_driver_data reg_data; > + struct msm_spm_power_modes *modes; > + uint32_t num_modes; > + uint32_t cpu_vdd; > + struct cpumask mask; > + void __iomem *q2s_reg; > +}; > + > +struct msm_spm_vdd_info { > + struct msm_spm_device *vctl_dev; > + uint32_t vlevel; > + int err; > +}; > + > +static LIST_HEAD(spm_list); > +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device); > +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device); > + > +static void msm_spm_smp_set_vdd(void *data) > +{ > + struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data; > + struct msm_spm_device *dev = info->vctl_dev; > + > + dev->cpu_vdd = info->vlevel; > + info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel); > +} > + > +/** > + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2 > + * probe. > + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER. > + * if probe failed, then return the err number for that failure. > + */ > +int msm_spm_probe_done(void) > +{ > + struct msm_spm_device *dev; > + int cpu; > + int ret = 0; > + > + for_each_possible_cpu(cpu) { > + dev = per_cpu(cpu_vctl_device, cpu); > + if (!dev) > + return -EPROBE_DEFER; > + > + ret = IS_ERR(dev); > + if (ret) > + return ret; > + } > + > + return 0; > +} > +EXPORT_SYMBOL(msm_spm_probe_done); Can you explain how this function is used by the caller ? When is it called ? What is its purpose and who is setting 'dev' to an ERR ? > +void msm_spm_dump_regs(unsigned int cpu) > +{ > + dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu); > +} > + > +/** > + * msm_spm_set_vdd(): Set core voltage > + * @cpu: core id > + * @vlevel: Encoded PMIC data. > + * > + * Return: 0 on success or -(ERRNO) on failure. > + */ > +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) > +{ > + struct msm_spm_vdd_info info; > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + int ret; > + > + if (!dev) > + return -EPROBE_DEFER; > + > + ret = IS_ERR(dev); > + if (ret) > + return ret; > + > + info.vctl_dev = dev; > + info.vlevel = vlevel; > + > + ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info, > + true); > + if (ret) > + return ret; If cpu_vctl_device is per cpu, why a cpumask is used ? > + > + return info.err; > +} > +EXPORT_SYMBOL(msm_spm_set_vdd); > + > +/** > + * msm_spm_get_vdd(): Get core voltage > + * @cpu: core id > + * @return: Returns encoded PMIC data. > + */ > +unsigned int msm_spm_get_vdd(unsigned int cpu) > +{ > + int ret; > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + if (!dev) > + return -EPROBE_DEFER; > + > + ret = IS_ERR(dev); > + if (ret) > + return ret; > + > + return dev->cpu_vdd; > +} > +EXPORT_SYMBOL(msm_spm_get_vdd); > + > +static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode) > +{ > + uint32_t spm_legacy_mode = 0; > + uint32_t qchannel_ignore = 0; > + uint32_t val = 0; > + > + if (!dev->q2s_reg) > + return; > + > + switch (mode) { > + case MSM_SPM_MODE_DISABLED: > + case MSM_SPM_MODE_CLOCK_GATING: > + qchannel_ignore = 1; > + spm_legacy_mode = 0; > + break; > + case MSM_SPM_MODE_RETENTION: > + qchannel_ignore = 0; > + spm_legacy_mode = 0; > + break; > + case MSM_SPM_MODE_GDHS: > + case MSM_SPM_MODE_POWER_COLLAPSE: > + qchannel_ignore = 0; > + spm_legacy_mode = 1; > + break; > + default: > + break; > + } > + > + val = spm_legacy_mode << 2 | qchannel_ignore << 1; > + __raw_writel(val, dev->q2s_reg); > + mb(); > +} > + > +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm) > +{ > + uint32_t i; > + uint32_t start_addr = 0; > + int ret = -EINVAL; > + bool pc_mode = false; > + > + if (!dev->initialized) > + return -ENXIO; > + > + if ((mode == MSM_SPM_MODE_POWER_COLLAPSE) > + || (mode == MSM_SPM_MODE_GDHS)) > + pc_mode = true; > + > + if (mode == MSM_SPM_MODE_DISABLED) { > + ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false); > + } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) { > + for (i = 0; i < dev->num_modes; i++) { > + if ((dev->modes[i].mode == mode) && > + (dev->modes[i].notify_rpm == notify_rpm)) { > + start_addr = dev->modes[i].start_addr; > + break; > + } > + } > + ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, > + start_addr, pc_mode); > + } > + > + msm_spm_config_q2s(dev, mode); > + > + return ret; > +} > + > +static int msm_spm_dev_init(struct msm_spm_device *dev, > + struct msm_spm_platform_data *data) > +{ > + int i, ret = -ENOMEM; > + uint32_t offset = 0; > + > + dev->cpu_vdd = VDD_DEFAULT; > + dev->num_modes = data->num_modes; > + dev->modes = kmalloc( > + sizeof(struct msm_spm_power_modes) * dev->num_modes, > + GFP_KERNEL); > + > + if (!dev->modes) > + goto spm_failed_malloc; > + > + dev->reg_data.major = data->major; > + dev->reg_data.minor = data->minor; > + ret = msm_spm_drv_init(&dev->reg_data, data); > + > + if (ret) > + goto spm_failed_init; > + > + for (i = 0; i < dev->num_modes; i++) { > + > + /* Default offset is 0 and gets updated as we write more > + * sequences into SPM > + */ > + dev->modes[i].start_addr = offset; > + ret = msm_spm_drv_write_seq_data(&dev->reg_data, > + data->modes[i].cmd, &offset); > + if (ret < 0) > + goto spm_failed_init; > + > + dev->modes[i].mode = data->modes[i].mode; > + dev->modes[i].notify_rpm = data->modes[i].notify_rpm; > + } > + msm_spm_drv_reinit(&dev->reg_data); > + dev->initialized = true; > + return 0; > + > +spm_failed_init: > + kfree(dev->modes); > +spm_failed_malloc: > + return ret; > +} > + > +/** > + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core > + * @base: The SAW VCTL register which would set the voltage up. > + * @val: The value to be set on the rail > + * @cpu: The cpu for this with rail is being powered on > + */ > +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu) > +{ > + uint32_t timeout = 2000; /* delay for voltage to settle on the core */ > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + /* > + * If clock drivers have already set up the voltage, > + * do not overwrite that value. > + */ > + if (dev && (dev->cpu_vdd != VDD_DEFAULT)) > + return 0; > + > + /* Set the CPU supply regulator voltage */ > + val = (val & 0xFF); > + writel_relaxed(val, base); > + mb(); > + udelay(timeout); > + > + /* Enable the CPU supply regulator*/ > + val = 0x30080; > + writel_relaxed(val, base); > + mb(); > + udelay(timeout); > + > + return 0; > +} > +EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail); > + > +void msm_spm_reinit(void) > +{ > + unsigned int cpu; > + > + for_each_possible_cpu(cpu) > + msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu)); > +} > +EXPORT_SYMBOL(msm_spm_reinit); > + > +/* > + * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu > + * It should only be used to decide a mode before lpm driver is probed. > + * @mode: SPM LPM mode to be selected > + */ > +bool msm_spm_is_mode_avail(unsigned int mode) > +{ > + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); > + int i; > + > + for (i = 0; i < dev->num_modes; i++) { > + if (dev->modes[i].mode == mode) > + return true; > + } > + > + return false; > +} > + > +/** > + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode > + * @mode: SPM LPM mode to enter > + * @notify_rpm: Notify RPM in this mode > + */ > +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) > +{ > + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); > + > + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); > +} > +EXPORT_SYMBOL(msm_spm_set_low_power_mode); > + > +/** > + * msm_spm_init(): Board initalization function > + * @data: platform specific SPM register configuration data > + * @nr_devs: Number of SPM devices being initialized > + */ > +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) > +{ > + unsigned int cpu; > + int ret = 0; > + > + BUG_ON((nr_devs < num_possible_cpus()) || !data); > + > + for_each_possible_cpu(cpu) { > + struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu); > + > + ret = msm_spm_dev_init(dev, &data[cpu]); > + if (ret < 0) { > + pr_warn("%s():failed CPU:%u ret:%d\n", __func__, > + cpu, ret); > + break; > + } > + } > + > + return ret; > +} > + > +struct msm_spm_device *msm_spm_get_device_by_name(const char *name) > +{ > + struct list_head *list; > + > + list_for_each(list, &spm_list) { > + struct msm_spm_device *dev > + = list_entry(list, typeof(*dev), list); > + if (dev->name && !strcmp(dev->name, name)) > + return dev; > + } > + return ERR_PTR(-ENODEV); > +} > + > +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm) > +{ > + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); > +} > +#ifdef CONFIG_MSM_L2_SPM > + > +/** > + * msm_spm_apcs_set_phase(): Set number of SMPS phases. > + * @cpu: cpu which is requesting the change in number of phases. > + * @phase_cnt: Number of phases to be set active > + */ > +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) > +{ > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + if (!dev) > + return -ENXIO; > + > + return msm_spm_drv_set_pmic_data(&dev->reg_data, > + MSM_SPM_PMIC_PHASE_PORT, phase_cnt); > +} > +EXPORT_SYMBOL(msm_spm_apcs_set_phase); > + > +/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power > + * when the cores are in low power modes > + * @cpu: cpu that is entering low power mode. > + * @mode: The mode configuration for FTS > + */ > +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) > +{ > + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); > + > + if (!dev) > + return -ENXIO; > + > + return msm_spm_drv_set_pmic_data(&dev->reg_data, > + MSM_SPM_PMIC_PFM_PORT, mode); > +} > +EXPORT_SYMBOL(msm_spm_enable_fts_lpm); > + > +#endif > + > +static int get_cpu_id(struct device_node *node) > +{ > + struct device_node *cpu_node; > + u32 cpu; > + int ret = -EINVAL; > + char *key = "qcom,cpu"; > + > + cpu_node = of_parse_phandle(node, key, 0); > + if (cpu_node) { > + for_each_possible_cpu(cpu) { > + if (of_get_cpu_node(cpu, NULL) == cpu_node) > + return cpu; > + } > + } > + return ret; > +} > + > +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev) > +{ > + struct msm_spm_device *dev = NULL; > + const char *val = NULL; > + char *key = "qcom,name"; > + int cpu = get_cpu_id(pdev->dev.of_node); > + > + if ((cpu >= 0) && cpu < num_possible_cpus()) > + dev = &per_cpu(msm_cpu_spm_device, cpu); > + else if ((cpu == 0xffff) || (cpu < 0)) > + dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device), > + GFP_KERNEL); > + > + if (!dev) > + return NULL; > + > + if (of_property_read_string(pdev->dev.of_node, key, &val)) { > + pr_err("%s(): Cannot find a required node key:%s\n", > + __func__, key); > + return NULL; > + } > + dev->name = val; Is the string pointed by val always valid ? > + list_add(&dev->list, &spm_list); > + > + return dev; > +} > + > +static void get_cpumask(struct device_node *node, struct cpumask *mask) > +{ > + unsigned long vctl_mask = 0; > + unsigned c = 0; > + int idx = 0; > + struct device_node *cpu_node = NULL; > + int ret = 0; > + char *key = "qcom,cpu-vctl-list"; > + bool found = false; > + > + cpu_node = of_parse_phandle(node, key, idx++); > + while (cpu_node) { > + found = true; > + for_each_possible_cpu(c) { > + if (of_get_cpu_node(c, NULL) == cpu_node) > + cpumask_set_cpu(c, mask); > + } > + cpu_node = of_parse_phandle(node, key, idx++); > + }; > + > + if (found) > + return; > + > + key = "qcom,cpu-vctl-mask"; > + ret = of_property_read_u32(node, key, (u32 *) &vctl_mask); > + if (!ret) { > + for_each_set_bit(c, &vctl_mask, num_possible_cpus()) { > + cpumask_set_cpu(c, mask); > + } > + } > +} > + > +static int msm_spm_dev_probe(struct platform_device *pdev) > +{ > + int ret = 0; > + int cpu = 0; > + int i = 0; > + struct device_node *node = pdev->dev.of_node; > + struct msm_spm_platform_data spm_data; > + char *key = NULL; > + uint32_t val = 0; > + struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR]; > + int len = 0; > + struct msm_spm_device *dev = NULL; > + struct resource *res = NULL; > + uint32_t mode_count = 0; > + > + struct spm_of { > + char *key; > + uint32_t id; > + }; > + > + struct spm_of spm_of_data[] = { > + {"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG}, > + {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY}, > + {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL}, > + {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0}, > + {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1}, > + {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2}, > + {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3}, > + {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4}, > + {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5}, > + {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6}, > + {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7}, > + }; > + > + struct mode_of { > + char *key; > + uint32_t id; > + uint32_t notify_rpm; > + }; > + > + struct mode_of mode_of_data[] = { > + {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0}, > + {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0}, > + {"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1}, > + {"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0}, > + {"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1}, > + }; > + > + dev = msm_spm_get_device(pdev); > + if (!dev) { > + ret = -ENOMEM; > + goto fail; > + } > + get_cpumask(node, &dev->mask); > + > + memset(&spm_data, 0, sizeof(struct msm_spm_platform_data)); > + memset(&modes, 0, > + (MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry)); > + > + if (of_device_is_compatible(node, "qcom,spm-v2.1")) { > + spm_data.major = 2; > + spm_data.minor = 1; > + } else if (of_device_is_compatible(node, "qcom,spm-v3.0")) { > + spm_data.major = 3; > + spm_data.minor = 0; > + } > + > + key = "qcom,vctl-timeout-us"; > + ret = of_property_read_u32(node, key, &val); > + if (!ret) > + spm_data.vctl_timeout_us = val; > + > + /* SAW start address */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + ret = -EFAULT; > + goto fail; > + } > + > + spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start, > + resource_size(res)); > + if (!spm_data.reg_base_addr) { > + ret = -ENOMEM; > + goto fail; > + } > + > + spm_data.vctl_port = -1; > + spm_data.phase_port = -1; > + spm_data.pfm_port = -1; > + > + key = "qcom,vctl-port"; > + of_property_read_u32(node, key, &spm_data.vctl_port); > + > + key = "qcom,phase-port"; > + of_property_read_u32(node, key, &spm_data.phase_port); > + > + key = "qcom,pfm-port"; > + of_property_read_u32(node, key, &spm_data.pfm_port); > + > + /* Q2S (QChannel-2-SPM) register */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); > + if (res) { > + dev->q2s_reg = devm_ioremap(&pdev->dev, res->start, > + resource_size(res)); > + if (!dev->q2s_reg) { > + pr_err("%s(): Unable to iomap Q2S register\n", > + __func__); > + ret = -EADDRNOTAVAIL; > + goto fail; > + } > + } > + /* > + * At system boot, cpus and or clusters can remain in reset. CCI SPM > + * will not be triggered unless SPM_LEGACY_MODE bit is set for the > + * cluster in reset. Initialize q2s registers and set the > + * SPM_LEGACY_MODE bit. > + */ > + msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE); > + > + for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) { > + ret = of_property_read_u32(node, spm_of_data[i].key, &val); > + if (ret) > + continue; > + spm_data.reg_init_values[spm_of_data[i].id] = val; > + } > + > + for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) { > + key = mode_of_data[i].key; > + modes[mode_count].cmd = > + (uint8_t *)of_get_property(node, key, &len); > + if (!modes[mode_count].cmd) > + continue; > + modes[mode_count].mode = mode_of_data[i].id; > + modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm; > + pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__, > + dev->name, key, modes[mode_count].mode, > + modes[mode_count].notify_rpm); > + mode_count++; > + } > + > + spm_data.modes = modes; > + spm_data.num_modes = mode_count; > + > + ret = msm_spm_dev_init(dev, &spm_data); > + if (ret) > + goto fail; > + > + platform_set_drvdata(pdev, dev); > + > + for_each_cpu(cpu, &dev->mask) > + per_cpu(cpu_vctl_device, cpu) = dev; > + > + return ret; > + > +fail: > + cpu = get_cpu_id(pdev->dev.of_node); > + if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) { > + for_each_cpu(cpu, &dev->mask) > + per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret); > + } > + > + pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret); > + > + return ret; > +} > + > +static int msm_spm_dev_remove(struct platform_device *pdev) > +{ > + struct msm_spm_device *dev = platform_get_drvdata(pdev); > + > + list_del(&dev->list); > + > + return 0; > +} > + > +static struct of_device_id msm_spm_match_table[] = { > + {.compatible = "qcom,spm-v2.1"}, > + {.compatible = "qcom,spm-v3.0"}, > + {}, > +}; > + > +static struct platform_driver msm_spm_device_driver = { > + .probe = msm_spm_dev_probe, > + .remove = msm_spm_dev_remove, > + .driver = { > + .name = "spm-v2", > + .owner = THIS_MODULE, > + .of_match_table = msm_spm_match_table, > + }, > +}; > + > +/** > + * msm_spm_device_init(): Device tree initialization function > + */ > +int __init msm_spm_device_init(void) > +{ > + static bool registered; > + > + if (registered) > + return 0; > + > + registered = true; > + > + return platform_driver_register(&msm_spm_device_driver); > +} > +device_initcall(msm_spm_device_init); Why is needed this 'registered' thing ? Couldn't the msm_spm_device_init be removed and replaced by: module_platform_driver(msm_spm_device_driver); ? > diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c > new file mode 100644 > index 0000000..7dbdb64 > --- /dev/null > +++ b/drivers/soc/qcom/spm.c > @@ -0,0 +1,482 @@ > +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + */ > + > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/delay.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/slab.h> > + > +#include "spm_driver.h" > + > +#define MSM_SPM_PMIC_STATE_IDLE 0 > + > +enum { > + MSM_SPM_DEBUG_SHADOW = 1U << 0, > + MSM_SPM_DEBUG_VCTL = 1U << 1, > +}; > + > +static int msm_spm_debug_mask; > +module_param_named( > + debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP > +); > + > +struct saw2_data { > + const char *ver_name; > + uint32_t major; > + uint32_t minor; > + uint32_t *spm_reg_offset_ptr; > +}; > + > +static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = { > + [MSM_SPM_REG_SAW2_SECURE] = 0x00, > + [MSM_SPM_REG_SAW2_ID] = 0x04, > + [MSM_SPM_REG_SAW2_CFG] = 0x08, > + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, > + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, > + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, > + [MSM_SPM_REG_SAW2_RST] = 0x18, > + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, > + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, > + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, > + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, > + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, > + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, > + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, > + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, > + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, > + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, > + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, > + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, > + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, > + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, > + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, > + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, > + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, > +}; > + > +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = { > + [MSM_SPM_REG_SAW2_SECURE] = 0x00, > + [MSM_SPM_REG_SAW2_ID] = 0x04, > + [MSM_SPM_REG_SAW2_CFG] = 0x08, > + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, > + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, > + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, > + [MSM_SPM_REG_SAW2_RST] = 0x18, > + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, > + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, > + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, > + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, > + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, > + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, > + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, > + [MSM_SPM_REG_SAW2_STS2] = 0x38, > + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, > + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, > + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, > + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, > + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, > + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, > + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, > + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, > + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x400, > + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, > +}; > + > +static struct saw2_data saw2_info[] = { > + [0] = { > + "SAW2_v2.1", > + 2, > + 1, > + msm_spm_reg_offsets_saw2_v2_1, > + }, > + [1] = { > + "SAW2_v3.0", > + 3, > + 0, > + msm_spm_reg_offsets_saw2_v3_0, > + }, > +}; > + > +static uint32_t num_pmic_data; > + > +static inline uint32_t msm_spm_drv_get_num_spm_entry( > + struct msm_spm_driver_data *dev) > +{ > + return 32; > +} > + > +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, > + unsigned int reg_index) > +{ > + __raw_writel(dev->reg_shadow[reg_index], > + dev->reg_base_addr + dev->reg_offsets[reg_index]); > +} > + > +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, > + unsigned int reg_index) > +{ > + dev->reg_shadow[reg_index] = > + __raw_readl(dev->reg_base_addr + > + dev->reg_offsets[reg_index]); > +} > + > +static inline void msm_spm_drv_set_start_addr( > + struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode) > +{ > + addr &= 0x7F; > + addr <<= 4; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; > + > + if (dev->major != 0x3) > + return; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF; > + if (pc_mode) > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000; > +} > + > +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; > +} > + > +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, > + uint32_t vlevel) > +{ > + unsigned int pmic_data = 0; > + > + /** > + * VCTL_PORT has to be 0, for PMIC_STS register to be updated. > + * Ensure that vctl_port is always set to 0. > + */ > + WARN_ON(dev->vctl_port); > + > + pmic_data |= vlevel; > + pmic_data |= (dev->vctl_port & 0x7) << 16; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data; > + > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3); > +} > + > +static inline uint32_t msm_spm_drv_get_num_pmic_data( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); > + mb(); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7; > +} > + > +static inline uint32_t msm_spm_drv_get_sts_pmic_state( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & > + 0x03; > +} > + > +uint32_t msm_spm_drv_get_sts_curr_pmic_data( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); > + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; > +} > + > +inline int msm_spm_drv_set_spm_enable( > + struct msm_spm_driver_data *dev, bool enable) > +{ > + uint32_t value = enable ? 0x01 : 0x00; > + > + if (!dev) > + return -EINVAL; > + > + if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; > + > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); > + wmb(); > + } > + return 0; > +} > +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev) > +{ > + int i; > + int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); > + > + if (!dev) { > + __WARN(); > + return; > + } > + > + for (i = 0; i < num_spm_entry; i++) { > + __raw_writel(dev->reg_seq_entry_shadow[i], > + dev->reg_base_addr > + + dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] > + + 4 * i); > + } > + mb(); > +} > + > +void dump_regs(struct msm_spm_driver_data *dev, int cpu) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); > + mb(); > + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu, > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]); > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); > + mb(); > + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu, > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]); > +} > + > +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, > + uint8_t *cmd, uint32_t *offset) > +{ > + uint32_t cmd_w; > + uint32_t offset_w = *offset / 4; > + uint8_t last_cmd; > + > + if (!cmd) > + return -EINVAL; > + > + while (1) { > + int i; > + > + cmd_w = 0; > + last_cmd = 0; > + cmd_w = dev->reg_seq_entry_shadow[offset_w]; > + > + for (i = (*offset % 4); i < 4; i++) { > + last_cmd = *(cmd++); > + cmd_w |= last_cmd << (i * 8); > + (*offset)++; > + if (last_cmd == 0x0f) > + break; > + } > + > + dev->reg_seq_entry_shadow[offset_w++] = cmd_w; > + if (last_cmd == 0x0f) > + break; > + } > + > + return 0; > +} > + > +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, > + uint32_t addr, bool pc_mode) > +{ > + > + if (!dev) > + return -EINVAL; > + > + msm_spm_drv_set_start_addr(dev, addr, pc_mode); > + > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); > + wmb(); > + > + if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { > + int i; > + > + for (i = 0; i < MSM_SPM_REG_NR; i++) > + pr_info("%s: reg %02x = 0x%08x\n", __func__, > + dev->reg_offsets[i], dev->reg_shadow[i]); > + } > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); > + > + return 0; > +} > + > +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel) > +{ > + uint32_t timeout_us, new_level; > + > + if (!dev) > + return -EINVAL; > + > + if (!msm_spm_pmic_arb_present(dev)) > + return -ENOSYS; > + > + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) > + pr_info("%s: requesting vlevel %#x\n", __func__, vlevel); > + > + /* Kick the state machine back to idle */ > + dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1; > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST); > + > + msm_spm_drv_set_vctl2(dev, vlevel); > + > + timeout_us = dev->vctl_timeout_us; > + /* Confirm the voltage we set was what hardware sent */ > + do { > + new_level = msm_spm_drv_get_sts_curr_pmic_data(dev); > + if (new_level == vlevel) > + break; > + udelay(1); > + } while (--timeout_us); > + if (!timeout_us) { > + pr_info("Wrong level %#x\n", new_leve > + goto set_vdd_bail; > + } > + > + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) > + pr_info("%s: done, remaining timeout %u us\n", > + __func__, timeout_us); > + > + return 0; > + > +set_vdd_bail: > + pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n", > + __func__, vlevel, timeout_us, new_level); > + return -EIO; > +} > + > +static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev, > + enum msm_spm_pmic_port port) > +{ > + int index = -1; > + > + switch (port) { > + case MSM_SPM_PMIC_VCTL_PORT: > + index = dev->vctl_port; > + break; > + case MSM_SPM_PMIC_PHASE_PORT: > + index = dev->phase_port; > + break; > + case MSM_SPM_PMIC_PFM_PORT: > + index = dev->pfm_port; > + break; > + default: > + break; > + } > + > + return index; > +} > + > +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, > + enum msm_spm_pmic_port port, unsigned int data) > +{ > + unsigned int pmic_data = 0; > + unsigned int timeout_us = 0; > + int index = 0; > + > + if (!msm_spm_pmic_arb_present(dev)) > + return -ENOSYS; > + > + index = msm_spm_drv_get_pmic_port(dev, port); > + if (index < 0) > + return -ENODEV; > + > + pmic_data |= data & 0xFF; > + pmic_data |= (index & 0x7) << 16; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); > + mb(); > + > + timeout_us = dev->vctl_timeout_us; > + /** > + * Confirm the pmic data set was what hardware sent by > + * checking the PMIC FSM state. > + * We cannot use the sts_pmic_data and check it against > + * the value like we do fot set_vdd, since the PMIC_STS > + * is only updated for SAW_VCTL sent with port index 0. > + */ > + do { > + if (msm_spm_drv_get_sts_pmic_state(dev) == > + MSM_SPM_PMIC_STATE_IDLE) > + break; > + udelay(1); > + } while (--timeout_us); > + > + if (!timeout_us) { > + pr_err("%s: failed, remaining timeout %u us, data %d\n", > + __func__, timeout_us, data); > + return -EIO; > + } > + > + return 0; > +} > + > +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev) > +{ > + int i; > + > + msm_spm_drv_flush_seq_entry(dev); > + for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++) > + msm_spm_drv_flush_shadow(dev, i); > + > + mb(); Why are needed the mb() after calling the msm_spm_drv_flush_shadow function ? > + > + for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++) > + msm_spm_drv_load_shadow(dev, i); > +} > + > +int msm_spm_drv_init(struct msm_spm_driver_data *dev, > + struct msm_spm_platform_data *data) > +{ > + int i; > + int num_spm_entry; > + bool found = false; > + > + BUG_ON(!dev || !data); > + > + dev->vctl_port = data->vctl_port; > + dev->phase_port = data->phase_port; > + dev->pfm_port = data->pfm_port; > + dev->reg_base_addr = data->reg_base_addr; > + memcpy(dev->reg_shadow, data->reg_init_values, > + sizeof(data->reg_init_values)); > + > + dev->vctl_timeout_us = data->vctl_timeout_us; > + > + for (i = 0; i < ARRAY_SIZE(saw2_info); i++) > + if (dev->major == saw2_info[i].major && > + dev->minor == saw2_info[i].minor) { > + pr_debug("%s: Version found\n", > + saw2_info[i].ver_name); > + dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr; > + found = true; > + break; > + } > + > + if (!found) { > + pr_err("%s: No SAW2 version found\n", __func__); > + BUG_ON(!found); > + } > + > + if (!num_pmic_data) > + num_pmic_data = msm_spm_drv_get_num_pmic_data(dev); > + > + num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); > + > + dev->reg_seq_entry_shadow = > + kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry, > + GFP_KERNEL); > + > + if (!dev->reg_seq_entry_shadow) > + return -ENOMEM; > + > + return 0; > +} > diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h > new file mode 100644 > index 0000000..b306520 > --- /dev/null > +++ b/drivers/soc/qcom/spm_driver.h > @@ -0,0 +1,116 @@ > +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > +#ifndef __QCOM_SPM_DRIVER_H > +#define __QCOM_SPM_DRIVER_H > + > +#include <soc/qcom/spm.h> > + > +enum { > + MSM_SPM_REG_SAW2_CFG, > + MSM_SPM_REG_SAW2_AVS_CTL, > + MSM_SPM_REG_SAW2_AVS_HYSTERESIS, > + MSM_SPM_REG_SAW2_SPM_CTL, > + MSM_SPM_REG_SAW2_PMIC_DLY, > + MSM_SPM_REG_SAW2_AVS_LIMIT, > + MSM_SPM_REG_SAW2_AVS_DLY, > + MSM_SPM_REG_SAW2_SPM_DLY, > + MSM_SPM_REG_SAW2_PMIC_DATA_0, > + MSM_SPM_REG_SAW2_PMIC_DATA_1, > + MSM_SPM_REG_SAW2_PMIC_DATA_2, > + MSM_SPM_REG_SAW2_PMIC_DATA_3, > + MSM_SPM_REG_SAW2_PMIC_DATA_4, > + MSM_SPM_REG_SAW2_PMIC_DATA_5, > + MSM_SPM_REG_SAW2_PMIC_DATA_6, > + MSM_SPM_REG_SAW2_PMIC_DATA_7, > + MSM_SPM_REG_SAW2_RST, > + > + MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST, > + > + MSM_SPM_REG_SAW2_ID, > + MSM_SPM_REG_SAW2_SECURE, > + MSM_SPM_REG_SAW2_STS0, > + MSM_SPM_REG_SAW2_STS1, > + MSM_SPM_REG_SAW2_STS2, > + MSM_SPM_REG_SAW2_VCTL, > + MSM_SPM_REG_SAW2_SEQ_ENTRY, > + MSM_SPM_REG_SAW2_SPM_STS, > + MSM_SPM_REG_SAW2_AVS_STS, > + MSM_SPM_REG_SAW2_PMIC_STS, > + MSM_SPM_REG_SAW2_VERSION, > + > + MSM_SPM_REG_NR, > +}; > + > +struct msm_spm_seq_entry { > + uint32_t mode; > + uint8_t *cmd; > + bool notify_rpm; > +}; > + > +struct msm_spm_platform_data { > + void __iomem *reg_base_addr; > + uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE]; > + > + uint32_t major; > + uint32_t minor; > + uint32_t vctl_port; > + uint32_t phase_port; > + uint32_t pfm_port; > + > + uint8_t awake_vlevel; > + uint32_t vctl_timeout_us; > + > + uint32_t num_modes; > + struct msm_spm_seq_entry *modes; > +}; > + > +enum msm_spm_pmic_port { > + MSM_SPM_PMIC_VCTL_PORT, > + MSM_SPM_PMIC_PHASE_PORT, > + MSM_SPM_PMIC_PFM_PORT, > +}; > + > +struct msm_spm_driver_data { > + uint32_t major; > + uint32_t minor; > + uint32_t vctl_port; > + uint32_t phase_port; > + uint32_t pfm_port; > + void __iomem *reg_base_addr; > + uint32_t vctl_timeout_us; > + uint32_t reg_shadow[MSM_SPM_REG_NR]; > + uint32_t *reg_seq_entry_shadow; > + uint32_t *reg_offsets; > +}; > + > +int msm_spm_drv_init(struct msm_spm_driver_data *dev, > + struct msm_spm_platform_data *data); > +void msm_spm_drv_reinit(struct msm_spm_driver_data *dev); > +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, > + uint32_t addr, bool pc_mode); > +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, > + unsigned int vlevel); > +void dump_regs(struct msm_spm_driver_data *dev, int cpu); > +uint32_t msm_spm_drv_get_sts_curr_pmic_data( > + struct msm_spm_driver_data *dev); > +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, > + uint8_t *cmd, uint32_t *offset); > +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev); > +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, > + bool enable); > +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, > + enum msm_spm_pmic_port port, unsigned int data); > + > +void msm_spm_reinit(void); > +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs); > + > +#endif /* __QCOM_SPM_DRIVER_H */ > diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h > new file mode 100644 > index 0000000..f39e0c4 > --- /dev/null > +++ b/include/soc/qcom/spm.h > @@ -0,0 +1,70 @@ > +/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#ifndef __QCOM_SPM_H > +#define __QCOM_SPM_H > + > +enum { > + MSM_SPM_MODE_DISABLED, > + MSM_SPM_MODE_CLOCK_GATING, > + MSM_SPM_MODE_RETENTION, > + MSM_SPM_MODE_GDHS, > + MSM_SPM_MODE_POWER_COLLAPSE, > + MSM_SPM_MODE_NR > +}; > + > +struct msm_spm_device; > + > +#if defined(CONFIG_QCOM_PM) > +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); > +int msm_spm_probe_done(void); > +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel); > +unsigned int msm_spm_get_vdd(unsigned int cpu); > +int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu); > +struct msm_spm_device *msm_spm_get_device_by_name(const char *name); > +int msm_spm_config_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm); > +int msm_spm_device_init(void); > +bool msm_spm_is_mode_avail(unsigned int mode); > +void msm_spm_dump_regs(unsigned int cpu); > +int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt); > +int msm_spm_enable_fts_lpm(int cpu, uint32_t mode); > +#else /* defined(CONFIG_QCOM_PM) */ > +static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) > +{ return -ENOSYS; } > +static inline int msm_spm_probe_done(void) > +{ return -ENOSYS; } > +static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) > +{ return -ENOSYS; } > +static inline unsigned int msm_spm_get_vdd(unsigned int cpu) > +{ return 0; } > +static inline int msm_spm_turn_on_cpu_rail(void __iomem *base, > + unsigned int val, int cpu) > +{ return -ENOSYS; } > +static inline int msm_spm_device_init(void) > +{ return -ENOSYS; } > +static void msm_spm_dump_regs(unsigned int cpu) {} > +static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev, > + unsigned int mode, bool notify_rpm) > +{ return -ENODEV; } > +static inline struct msm_spm_device *msm_spm_get_device_by_name( > + const char *name) > +{ return NULL; } > +static inline bool msm_spm_is_mode_avail(unsigned int mode) > +{ return false; } > +static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) > +{ return -ENOSYS; } > +static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) > +{ return -ENOSYS; } > +#endif /* defined (CONFIG_QCOM_PM) */ > + > +#endif /* __QCOM_SPM_H */ >
On Wed, Aug 13, 2014 at 12:49:23PM +0200, Daniel Lezcano wrote: >On 08/12/2014 09:43 PM, Lina Iyer wrote: >>Qualcomm chipsets use an separate h/w block to control the logic around >>the processor cores (cpu and L2). The SPM h/w block regulates power to >>the cores and controls the power when the core enter low power modes. >> >>Each core has its own instance of SPM. The SPM has the following key >>functions >> - Configure the h/w dependencies when entering low power modes >> - Wait for interrupt and wake up on interrupt >> - Ensure the dependencies are ready before bringing the core out >> of sleep >> - Regulating voltage to the core, interfacing with the PMIC. >> - Optimize power based on runtime recommendations. >> >>The driver identifies and configures the SPMs, by reading the nodes and >>the register values from the devicetree. The SPMs need to be configured >>to allow the processor to be idled in a low power state. > >I began to comment but I realize I have a lot of questions and >comments for this patch and because of its size, it will be impossible >to follow a discussion. This patch is really too big to review, please >split it into smaller chunks. > >Thanks > > -- Daniel Hmm.. Tedious, but not impossible. Will get right on it. > >>Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org> >>Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org> >>Signed-off-by: Lina Iyer <lina.iyer@linaro.org> >>--- >> .../devicetree/bindings/arm/msm/spm-v2.txt | 62 ++ >> drivers/soc/qcom/Makefile | 2 + >> drivers/soc/qcom/spm-devices.c | 703 +++++++++++++++++++++ >> drivers/soc/qcom/spm.c | 482 ++++++++++++++ >> drivers/soc/qcom/spm_driver.h | 116 ++++ >> include/soc/qcom/spm.h | 70 ++ >> 6 files changed, 1435 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt >> create mode 100644 drivers/soc/qcom/spm-devices.c >> create mode 100644 drivers/soc/qcom/spm.c >> create mode 100644 drivers/soc/qcom/spm_driver.h >> create mode 100644 include/soc/qcom/spm.h >> >>diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt >>new file mode 100644 >>index 0000000..3130f4b >>--- /dev/null >>+++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt >>@@ -0,0 +1,62 @@ >>+* MSM Subsystem Power Manager (spm-v2) >>+ >>+S4 generation of MSMs have SPM hardware blocks to control the Application >>+Processor Sub-System power. These SPM blocks run individual state machine >>+to determine what the core (L2 or Krait/Scorpion) would do when the WFI >>+instruction is executed by the core. >>+ >>+The devicetree representation of the SPM block should be: >>+ >>+Required properties >>+ >>+- compatible: Could be one of - >>+ "qcom,spm-v2.1" >>+ "qcom,spm-v3.0" >>+- reg: The physical address and the size of the SPM's memory mapped registers >>+- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets >>+ that dont support CPU phandles the driver would support qcom,core-id. >>+ This field is required on only for SPMs that control the CPU. >>+- qcom,saw2-cfg: SAW2 configuration register >>+- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM >>+ sequence >>+- qcom,saw2-spm-ctl: The SPM control register >>+- qcom,name: The name with which a SPM device is identified by the power >>+ management code. >>+ >>+Optional properties >>+ >>+- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS >>+ (Fast Transient Switch) index to send the PMIC data to >>+- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing >>+ voltage >>+- qcom,phase-port: The PVC port used for changing the number of phases >>+- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes >>+- qcom,saw2-spm-cmd-wfi: The WFI command sequence >>+- qcom,saw2-spm-cmd-ret: The Retention command sequence >>+- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence >>+- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS >>+ proc won't inform the RPM. >>+- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may >>+ turn off other SoC components. >>+- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command >>+ sequence. This sequence will retain the memory but turn off the logic. >>+- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device >>+ can control. >>+- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to >>+ change after sending the voltage command to the PMIC. >>+- >>+Example: >>+ qcom,spm@f9089000 { >>+ compatible = "qcom,spm-v2"; >>+ #address-cells = <1>; >>+ #size-cells = <1>; >>+ reg = <0xf9089000 0x1000>; >>+ qcom,cpu = <&CPU0>; >>+ qcom,saw2-cfg = <0x1>; >>+ qcom,saw2-spm-dly= <0x20000400>; >>+ qcom,saw2-spm-ctl = <0x1>; >>+ qcom,saw2-spm-cmd-wfi = [03 0b 0f]; >>+ qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 >>+ a0 b0 03 68 70 3b 92 a0 b0 >>+ 82 2b 50 10 30 02 22 30 0f]; >>+ }; >>diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile >>index 70d52ed..d7ae93b 100644 >>--- a/drivers/soc/qcom/Makefile >>+++ b/drivers/soc/qcom/Makefile >>@@ -1,3 +1,5 @@ >> obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o >>+obj-$(CONFIG_QCOM_PM) += spm-devices.o spm.o >>+ >> CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) >> obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o >>diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c >>new file mode 100644 >>index 0000000..567e9f9 >>--- /dev/null >>+++ b/drivers/soc/qcom/spm-devices.c >>@@ -0,0 +1,703 @@ >>+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. >>+ * >>+ * This program is free software; you can redistribute it and/or modify >>+ * it under the terms of the GNU General Public License version 2 and >>+ * only version 2 as published by the Free Software Foundation. >>+ * >>+ * This program is distributed in the hope that it will be useful, >>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of >>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>+ * GNU General Public License for more details. >>+ * >>+ */ >>+ >>+#include <linux/module.h> >>+#include <linux/kernel.h> >>+#include <linux/delay.h> >>+#include <linux/init.h> >>+#include <linux/io.h> >>+#include <linux/slab.h> >>+#include <linux/of.h> >>+#include <linux/of_address.h> >>+#include <linux/err.h> >>+#include <linux/platform_device.h> >>+#include <linux/err.h> >>+ >>+#include <soc/qcom/spm.h> >>+ >>+#include "spm_driver.h" >>+ >>+#define VDD_DEFAULT 0xDEADF00D >>+ >>+struct msm_spm_power_modes { >>+ uint32_t mode; >>+ bool notify_rpm; >>+ uint32_t start_addr; >>+}; >>+ >>+struct msm_spm_device { >>+ struct list_head list; >>+ bool initialized; >>+ const char *name; >>+ struct msm_spm_driver_data reg_data; >>+ struct msm_spm_power_modes *modes; >>+ uint32_t num_modes; >>+ uint32_t cpu_vdd; >>+ struct cpumask mask; >>+ void __iomem *q2s_reg; >>+}; >>+ >>+struct msm_spm_vdd_info { >>+ struct msm_spm_device *vctl_dev; >>+ uint32_t vlevel; >>+ int err; >>+}; >>+ >>+static LIST_HEAD(spm_list); >>+static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device); >>+static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device); >>+ >>+static void msm_spm_smp_set_vdd(void *data) >>+{ >>+ struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data; >>+ struct msm_spm_device *dev = info->vctl_dev; >>+ >>+ dev->cpu_vdd = info->vlevel; >>+ info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel); >>+} >>+ >>+/** >>+ * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2 >>+ * probe. >>+ * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER. >>+ * if probe failed, then return the err number for that failure. >>+ */ >>+int msm_spm_probe_done(void) >>+{ >>+ struct msm_spm_device *dev; >>+ int cpu; >>+ int ret = 0; >>+ >>+ for_each_possible_cpu(cpu) { >>+ dev = per_cpu(cpu_vctl_device, cpu); >>+ if (!dev) >>+ return -EPROBE_DEFER; >>+ >>+ ret = IS_ERR(dev); >>+ if (ret) >>+ return ret; >>+ } >>+ >>+ return 0; >>+} >>+EXPORT_SYMBOL(msm_spm_probe_done); > >Can you explain how this function is used by the caller ? When is it >called ? What is its purpose and who is setting 'dev' to an ERR ? > >>+void msm_spm_dump_regs(unsigned int cpu) >>+{ >>+ dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu); >>+} >>+ >>+/** >>+ * msm_spm_set_vdd(): Set core voltage >>+ * @cpu: core id >>+ * @vlevel: Encoded PMIC data. >>+ * >>+ * Return: 0 on success or -(ERRNO) on failure. >>+ */ >>+int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) >>+{ >>+ struct msm_spm_vdd_info info; >>+ struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); >>+ int ret; >>+ >>+ if (!dev) >>+ return -EPROBE_DEFER; >>+ >>+ ret = IS_ERR(dev); >>+ if (ret) >>+ return ret; >>+ >>+ info.vctl_dev = dev; >>+ info.vlevel = vlevel; >>+ >>+ ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info, >>+ true); >>+ if (ret) >>+ return ret; > >If cpu_vctl_device is per cpu, why a cpumask is used ? > >>+ >>+ return info.err; >>+} >>+EXPORT_SYMBOL(msm_spm_set_vdd); >>+ >>+/** >>+ * msm_spm_get_vdd(): Get core voltage >>+ * @cpu: core id >>+ * @return: Returns encoded PMIC data. >>+ */ >>+unsigned int msm_spm_get_vdd(unsigned int cpu) >>+{ >>+ int ret; >>+ struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); >>+ >>+ if (!dev) >>+ return -EPROBE_DEFER; >>+ >>+ ret = IS_ERR(dev); >>+ if (ret) >>+ return ret; >>+ >>+ return dev->cpu_vdd; >>+} >>+EXPORT_SYMBOL(msm_spm_get_vdd); >>+ >>+static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode) >>+{ >>+ uint32_t spm_legacy_mode = 0; >>+ uint32_t qchannel_ignore = 0; >>+ uint32_t val = 0; >>+ >>+ if (!dev->q2s_reg) >>+ return; >>+ >>+ switch (mode) { >>+ case MSM_SPM_MODE_DISABLED: >>+ case MSM_SPM_MODE_CLOCK_GATING: >>+ qchannel_ignore = 1; >>+ spm_legacy_mode = 0; >>+ break; >>+ case MSM_SPM_MODE_RETENTION: >>+ qchannel_ignore = 0; >>+ spm_legacy_mode = 0; >>+ break; >>+ case MSM_SPM_MODE_GDHS: >>+ case MSM_SPM_MODE_POWER_COLLAPSE: >>+ qchannel_ignore = 0; >>+ spm_legacy_mode = 1; >>+ break; >>+ default: >>+ break; >>+ } >>+ >>+ val = spm_legacy_mode << 2 | qchannel_ignore << 1; >>+ __raw_writel(val, dev->q2s_reg); >>+ mb(); >>+} >>+ >>+static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, >>+ unsigned int mode, bool notify_rpm) >>+{ >>+ uint32_t i; >>+ uint32_t start_addr = 0; >>+ int ret = -EINVAL; >>+ bool pc_mode = false; >>+ >>+ if (!dev->initialized) >>+ return -ENXIO; >>+ >>+ if ((mode == MSM_SPM_MODE_POWER_COLLAPSE) >>+ || (mode == MSM_SPM_MODE_GDHS)) >>+ pc_mode = true; >>+ >>+ if (mode == MSM_SPM_MODE_DISABLED) { >>+ ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false); >>+ } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) { >>+ for (i = 0; i < dev->num_modes; i++) { >>+ if ((dev->modes[i].mode == mode) && >>+ (dev->modes[i].notify_rpm == notify_rpm)) { >>+ start_addr = dev->modes[i].start_addr; >>+ break; >>+ } >>+ } >>+ ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, >>+ start_addr, pc_mode); >>+ } >>+ >>+ msm_spm_config_q2s(dev, mode); >>+ >>+ return ret; >>+} >>+ >>+static int msm_spm_dev_init(struct msm_spm_device *dev, >>+ struct msm_spm_platform_data *data) >>+{ >>+ int i, ret = -ENOMEM; >>+ uint32_t offset = 0; >>+ >>+ dev->cpu_vdd = VDD_DEFAULT; >>+ dev->num_modes = data->num_modes; >>+ dev->modes = kmalloc( >>+ sizeof(struct msm_spm_power_modes) * dev->num_modes, >>+ GFP_KERNEL); >>+ >>+ if (!dev->modes) >>+ goto spm_failed_malloc; >>+ >>+ dev->reg_data.major = data->major; >>+ dev->reg_data.minor = data->minor; >>+ ret = msm_spm_drv_init(&dev->reg_data, data); >>+ >>+ if (ret) >>+ goto spm_failed_init; >>+ >>+ for (i = 0; i < dev->num_modes; i++) { >>+ >>+ /* Default offset is 0 and gets updated as we write more >>+ * sequences into SPM >>+ */ >>+ dev->modes[i].start_addr = offset; >>+ ret = msm_spm_drv_write_seq_data(&dev->reg_data, >>+ data->modes[i].cmd, &offset); >>+ if (ret < 0) >>+ goto spm_failed_init; >>+ >>+ dev->modes[i].mode = data->modes[i].mode; >>+ dev->modes[i].notify_rpm = data->modes[i].notify_rpm; >>+ } >>+ msm_spm_drv_reinit(&dev->reg_data); >>+ dev->initialized = true; >>+ return 0; >>+ >>+spm_failed_init: >>+ kfree(dev->modes); >>+spm_failed_malloc: >>+ return ret; >>+} >>+ >>+/** >>+ * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core >>+ * @base: The SAW VCTL register which would set the voltage up. >>+ * @val: The value to be set on the rail >>+ * @cpu: The cpu for this with rail is being powered on >>+ */ >>+int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu) >>+{ >>+ uint32_t timeout = 2000; /* delay for voltage to settle on the core */ >>+ struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); >>+ >>+ /* >>+ * If clock drivers have already set up the voltage, >>+ * do not overwrite that value. >>+ */ >>+ if (dev && (dev->cpu_vdd != VDD_DEFAULT)) >>+ return 0; >>+ >>+ /* Set the CPU supply regulator voltage */ >>+ val = (val & 0xFF); >>+ writel_relaxed(val, base); >>+ mb(); >>+ udelay(timeout); >>+ >>+ /* Enable the CPU supply regulator*/ >>+ val = 0x30080; >>+ writel_relaxed(val, base); >>+ mb(); >>+ udelay(timeout); >>+ >>+ return 0; >>+} >>+EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail); >>+ >>+void msm_spm_reinit(void) >>+{ >>+ unsigned int cpu; >>+ >>+ for_each_possible_cpu(cpu) >>+ msm_spm_drv_reinit(&per_cpu(msm_cpu_spm_device.reg_data, cpu)); >>+} >>+EXPORT_SYMBOL(msm_spm_reinit); >>+ >>+/* >>+ * msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu >>+ * It should only be used to decide a mode before lpm driver is probed. >>+ * @mode: SPM LPM mode to be selected >>+ */ >>+bool msm_spm_is_mode_avail(unsigned int mode) >>+{ >>+ struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); >>+ int i; >>+ >>+ for (i = 0; i < dev->num_modes; i++) { >>+ if (dev->modes[i].mode == mode) >>+ return true; >>+ } >>+ >>+ return false; >>+} >>+ >>+/** >>+ * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode >>+ * @mode: SPM LPM mode to enter >>+ * @notify_rpm: Notify RPM in this mode >>+ */ >>+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) >>+{ >>+ struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); >>+ >>+ return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); >>+} >>+EXPORT_SYMBOL(msm_spm_set_low_power_mode); >>+ >>+/** >>+ * msm_spm_init(): Board initalization function >>+ * @data: platform specific SPM register configuration data >>+ * @nr_devs: Number of SPM devices being initialized >>+ */ >>+int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) >>+{ >>+ unsigned int cpu; >>+ int ret = 0; >>+ >>+ BUG_ON((nr_devs < num_possible_cpus()) || !data); >>+ >>+ for_each_possible_cpu(cpu) { >>+ struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu); >>+ >>+ ret = msm_spm_dev_init(dev, &data[cpu]); >>+ if (ret < 0) { >>+ pr_warn("%s():failed CPU:%u ret:%d\n", __func__, >>+ cpu, ret); >>+ break; >>+ } >>+ } >>+ >>+ return ret; >>+} >>+ >>+struct msm_spm_device *msm_spm_get_device_by_name(const char *name) >>+{ >>+ struct list_head *list; >>+ >>+ list_for_each(list, &spm_list) { >>+ struct msm_spm_device *dev >>+ = list_entry(list, typeof(*dev), list); >>+ if (dev->name && !strcmp(dev->name, name)) >>+ return dev; >>+ } >>+ return ERR_PTR(-ENODEV); >>+} >>+ >>+int msm_spm_config_low_power_mode(struct msm_spm_device *dev, >>+ unsigned int mode, bool notify_rpm) >>+{ >>+ return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); >>+} >>+#ifdef CONFIG_MSM_L2_SPM >>+ >>+/** >>+ * msm_spm_apcs_set_phase(): Set number of SMPS phases. >>+ * @cpu: cpu which is requesting the change in number of phases. >>+ * @phase_cnt: Number of phases to be set active >>+ */ >>+int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) >>+{ >>+ struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); >>+ >>+ if (!dev) >>+ return -ENXIO; >>+ >>+ return msm_spm_drv_set_pmic_data(&dev->reg_data, >>+ MSM_SPM_PMIC_PHASE_PORT, phase_cnt); >>+} >>+EXPORT_SYMBOL(msm_spm_apcs_set_phase); >>+ >>+/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power >>+ * when the cores are in low power modes >>+ * @cpu: cpu that is entering low power mode. >>+ * @mode: The mode configuration for FTS >>+ */ >>+int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) >>+{ >>+ struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); >>+ >>+ if (!dev) >>+ return -ENXIO; >>+ >>+ return msm_spm_drv_set_pmic_data(&dev->reg_data, >>+ MSM_SPM_PMIC_PFM_PORT, mode); >>+} >>+EXPORT_SYMBOL(msm_spm_enable_fts_lpm); >>+ >>+#endif >>+ >>+static int get_cpu_id(struct device_node *node) >>+{ >>+ struct device_node *cpu_node; >>+ u32 cpu; >>+ int ret = -EINVAL; >>+ char *key = "qcom,cpu"; >>+ >>+ cpu_node = of_parse_phandle(node, key, 0); >>+ if (cpu_node) { >>+ for_each_possible_cpu(cpu) { >>+ if (of_get_cpu_node(cpu, NULL) == cpu_node) >>+ return cpu; >>+ } >>+ } >>+ return ret; >>+} >>+ >>+static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev) >>+{ >>+ struct msm_spm_device *dev = NULL; >>+ const char *val = NULL; >>+ char *key = "qcom,name"; >>+ int cpu = get_cpu_id(pdev->dev.of_node); >>+ >>+ if ((cpu >= 0) && cpu < num_possible_cpus()) >>+ dev = &per_cpu(msm_cpu_spm_device, cpu); >>+ else if ((cpu == 0xffff) || (cpu < 0)) >>+ dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device), >>+ GFP_KERNEL); >>+ >>+ if (!dev) >>+ return NULL; >>+ >>+ if (of_property_read_string(pdev->dev.of_node, key, &val)) { >>+ pr_err("%s(): Cannot find a required node key:%s\n", >>+ __func__, key); >>+ return NULL; >>+ } >>+ dev->name = val; > >Is the string pointed by val always valid ? > >>+ list_add(&dev->list, &spm_list); >>+ >>+ return dev; >>+} >>+ >>+static void get_cpumask(struct device_node *node, struct cpumask *mask) >>+{ >>+ unsigned long vctl_mask = 0; >>+ unsigned c = 0; >>+ int idx = 0; >>+ struct device_node *cpu_node = NULL; >>+ int ret = 0; >>+ char *key = "qcom,cpu-vctl-list"; >>+ bool found = false; >>+ >>+ cpu_node = of_parse_phandle(node, key, idx++); >>+ while (cpu_node) { >>+ found = true; >>+ for_each_possible_cpu(c) { >>+ if (of_get_cpu_node(c, NULL) == cpu_node) >>+ cpumask_set_cpu(c, mask); >>+ } >>+ cpu_node = of_parse_phandle(node, key, idx++); >>+ }; >>+ >>+ if (found) >>+ return; >>+ >>+ key = "qcom,cpu-vctl-mask"; >>+ ret = of_property_read_u32(node, key, (u32 *) &vctl_mask); >>+ if (!ret) { >>+ for_each_set_bit(c, &vctl_mask, num_possible_cpus()) { >>+ cpumask_set_cpu(c, mask); >>+ } >>+ } >>+} >>+ >>+static int msm_spm_dev_probe(struct platform_device *pdev) >>+{ >>+ int ret = 0; >>+ int cpu = 0; >>+ int i = 0; >>+ struct device_node *node = pdev->dev.of_node; >>+ struct msm_spm_platform_data spm_data; >>+ char *key = NULL; >>+ uint32_t val = 0; >>+ struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR]; >>+ int len = 0; >>+ struct msm_spm_device *dev = NULL; >>+ struct resource *res = NULL; >>+ uint32_t mode_count = 0; >>+ >>+ struct spm_of { >>+ char *key; >>+ uint32_t id; >>+ }; >>+ >>+ struct spm_of spm_of_data[] = { >>+ {"qcom,saw2-cfg", MSM_SPM_REG_SAW2_CFG}, >>+ {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW2_SPM_DLY}, >>+ {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW2_SPM_CTL}, >>+ {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW2_PMIC_DATA_0}, >>+ {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW2_PMIC_DATA_1}, >>+ {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW2_PMIC_DATA_2}, >>+ {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW2_PMIC_DATA_3}, >>+ {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW2_PMIC_DATA_4}, >>+ {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW2_PMIC_DATA_5}, >>+ {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW2_PMIC_DATA_6}, >>+ {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW2_PMIC_DATA_7}, >>+ }; >>+ >>+ struct mode_of { >>+ char *key; >>+ uint32_t id; >>+ uint32_t notify_rpm; >>+ }; >>+ >>+ struct mode_of mode_of_data[] = { >>+ {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING, 0}, >>+ {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION, 0}, >>+ {"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS, 1}, >>+ {"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE, 0}, >>+ {"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE, 1}, >>+ }; >>+ >>+ dev = msm_spm_get_device(pdev); >>+ if (!dev) { >>+ ret = -ENOMEM; >>+ goto fail; >>+ } >>+ get_cpumask(node, &dev->mask); >>+ >>+ memset(&spm_data, 0, sizeof(struct msm_spm_platform_data)); >>+ memset(&modes, 0, >>+ (MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry)); >>+ >>+ if (of_device_is_compatible(node, "qcom,spm-v2.1")) { >>+ spm_data.major = 2; >>+ spm_data.minor = 1; >>+ } else if (of_device_is_compatible(node, "qcom,spm-v3.0")) { >>+ spm_data.major = 3; >>+ spm_data.minor = 0; >>+ } >>+ >>+ key = "qcom,vctl-timeout-us"; >>+ ret = of_property_read_u32(node, key, &val); >>+ if (!ret) >>+ spm_data.vctl_timeout_us = val; >>+ >>+ /* SAW start address */ >>+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); >>+ if (!res) { >>+ ret = -EFAULT; >>+ goto fail; >>+ } >>+ >>+ spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start, >>+ resource_size(res)); >>+ if (!spm_data.reg_base_addr) { >>+ ret = -ENOMEM; >>+ goto fail; >>+ } >>+ >>+ spm_data.vctl_port = -1; >>+ spm_data.phase_port = -1; >>+ spm_data.pfm_port = -1; >>+ >>+ key = "qcom,vctl-port"; >>+ of_property_read_u32(node, key, &spm_data.vctl_port); >>+ >>+ key = "qcom,phase-port"; >>+ of_property_read_u32(node, key, &spm_data.phase_port); >>+ >>+ key = "qcom,pfm-port"; >>+ of_property_read_u32(node, key, &spm_data.pfm_port); >>+ >>+ /* Q2S (QChannel-2-SPM) register */ >>+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); >>+ if (res) { >>+ dev->q2s_reg = devm_ioremap(&pdev->dev, res->start, >>+ resource_size(res)); >>+ if (!dev->q2s_reg) { >>+ pr_err("%s(): Unable to iomap Q2S register\n", >>+ __func__); >>+ ret = -EADDRNOTAVAIL; >>+ goto fail; >>+ } >>+ } >>+ /* >>+ * At system boot, cpus and or clusters can remain in reset. CCI SPM >>+ * will not be triggered unless SPM_LEGACY_MODE bit is set for the >>+ * cluster in reset. Initialize q2s registers and set the >>+ * SPM_LEGACY_MODE bit. >>+ */ >>+ msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE); >>+ >>+ for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) { >>+ ret = of_property_read_u32(node, spm_of_data[i].key, &val); >>+ if (ret) >>+ continue; >>+ spm_data.reg_init_values[spm_of_data[i].id] = val; >>+ } >>+ >>+ for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) { >>+ key = mode_of_data[i].key; >>+ modes[mode_count].cmd = >>+ (uint8_t *)of_get_property(node, key, &len); >>+ if (!modes[mode_count].cmd) >>+ continue; >>+ modes[mode_count].mode = mode_of_data[i].id; >>+ modes[mode_count].notify_rpm = mode_of_data[i].notify_rpm; >>+ pr_debug("%s(): dev: %s cmd:%s, mode:%d rpm:%d\n", __func__, >>+ dev->name, key, modes[mode_count].mode, >>+ modes[mode_count].notify_rpm); >>+ mode_count++; >>+ } >>+ >>+ spm_data.modes = modes; >>+ spm_data.num_modes = mode_count; >>+ >>+ ret = msm_spm_dev_init(dev, &spm_data); >>+ if (ret) >>+ goto fail; >>+ >>+ platform_set_drvdata(pdev, dev); >>+ >>+ for_each_cpu(cpu, &dev->mask) >>+ per_cpu(cpu_vctl_device, cpu) = dev; >>+ >>+ return ret; >>+ >>+fail: >>+ cpu = get_cpu_id(pdev->dev.of_node); >>+ if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) { >>+ for_each_cpu(cpu, &dev->mask) >>+ per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret); >>+ } >>+ >>+ pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret); >>+ >>+ return ret; >>+} >>+ >>+static int msm_spm_dev_remove(struct platform_device *pdev) >>+{ >>+ struct msm_spm_device *dev = platform_get_drvdata(pdev); >>+ >>+ list_del(&dev->list); >>+ >>+ return 0; >>+} >>+ >>+static struct of_device_id msm_spm_match_table[] = { >>+ {.compatible = "qcom,spm-v2.1"}, >>+ {.compatible = "qcom,spm-v3.0"}, >>+ {}, >>+}; >>+ >>+static struct platform_driver msm_spm_device_driver = { >>+ .probe = msm_spm_dev_probe, >>+ .remove = msm_spm_dev_remove, >>+ .driver = { >>+ .name = "spm-v2", >>+ .owner = THIS_MODULE, >>+ .of_match_table = msm_spm_match_table, >>+ }, >>+}; >>+ >>+/** >>+ * msm_spm_device_init(): Device tree initialization function >>+ */ >>+int __init msm_spm_device_init(void) >>+{ >>+ static bool registered; >>+ >>+ if (registered) >>+ return 0; >>+ >>+ registered = true; >>+ >>+ return platform_driver_register(&msm_spm_device_driver); >>+} >>+device_initcall(msm_spm_device_init); > >Why is needed this 'registered' thing ? > >Couldn't the msm_spm_device_init be removed and replaced by: > >module_platform_driver(msm_spm_device_driver); > >? > >>diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c >>new file mode 100644 >>index 0000000..7dbdb64 >>--- /dev/null >>+++ b/drivers/soc/qcom/spm.c >>@@ -0,0 +1,482 @@ >>+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. >>+ * >>+ * This program is free software; you can redistribute it and/or modify >>+ * it under the terms of the GNU General Public License version 2 and >>+ * only version 2 as published by the Free Software Foundation. >>+ * >>+ * This program is distributed in the hope that it will be useful, >>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of >>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>+ * GNU General Public License for more details. >>+ * >>+ */ >>+ >>+#include <linux/module.h> >>+#include <linux/kernel.h> >>+#include <linux/delay.h> >>+#include <linux/init.h> >>+#include <linux/io.h> >>+#include <linux/slab.h> >>+ >>+#include "spm_driver.h" >>+ >>+#define MSM_SPM_PMIC_STATE_IDLE 0 >>+ >>+enum { >>+ MSM_SPM_DEBUG_SHADOW = 1U << 0, >>+ MSM_SPM_DEBUG_VCTL = 1U << 1, >>+}; >>+ >>+static int msm_spm_debug_mask; >>+module_param_named( >>+ debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP >>+); >>+ >>+struct saw2_data { >>+ const char *ver_name; >>+ uint32_t major; >>+ uint32_t minor; >>+ uint32_t *spm_reg_offset_ptr; >>+}; >>+ >>+static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = { >>+ [MSM_SPM_REG_SAW2_SECURE] = 0x00, >>+ [MSM_SPM_REG_SAW2_ID] = 0x04, >>+ [MSM_SPM_REG_SAW2_CFG] = 0x08, >>+ [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, >>+ [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, >>+ [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, >>+ [MSM_SPM_REG_SAW2_RST] = 0x18, >>+ [MSM_SPM_REG_SAW2_VCTL] = 0x1C, >>+ [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, >>+ [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, >>+ [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, >>+ [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, >>+ [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, >>+ [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, >>+ [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, >>+ [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, >>+}; >>+ >>+static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = { >>+ [MSM_SPM_REG_SAW2_SECURE] = 0x00, >>+ [MSM_SPM_REG_SAW2_ID] = 0x04, >>+ [MSM_SPM_REG_SAW2_CFG] = 0x08, >>+ [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, >>+ [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, >>+ [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, >>+ [MSM_SPM_REG_SAW2_RST] = 0x18, >>+ [MSM_SPM_REG_SAW2_VCTL] = 0x1C, >>+ [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, >>+ [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, >>+ [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, >>+ [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, >>+ [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, >>+ [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, >>+ [MSM_SPM_REG_SAW2_STS2] = 0x38, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, >>+ [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, >>+ [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x400, >>+ [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, >>+}; >>+ >>+static struct saw2_data saw2_info[] = { >>+ [0] = { >>+ "SAW2_v2.1", >>+ 2, >>+ 1, >>+ msm_spm_reg_offsets_saw2_v2_1, >>+ }, >>+ [1] = { >>+ "SAW2_v3.0", >>+ 3, >>+ 0, >>+ msm_spm_reg_offsets_saw2_v3_0, >>+ }, >>+}; >>+ >>+static uint32_t num_pmic_data; >>+ >>+static inline uint32_t msm_spm_drv_get_num_spm_entry( >>+ struct msm_spm_driver_data *dev) >>+{ >>+ return 32; >>+} >>+ >>+static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, >>+ unsigned int reg_index) >>+{ >>+ __raw_writel(dev->reg_shadow[reg_index], >>+ dev->reg_base_addr + dev->reg_offsets[reg_index]); >>+} >>+ >>+static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, >>+ unsigned int reg_index) >>+{ >>+ dev->reg_shadow[reg_index] = >>+ __raw_readl(dev->reg_base_addr + >>+ dev->reg_offsets[reg_index]); >>+} >>+ >>+static inline void msm_spm_drv_set_start_addr( >>+ struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode) >>+{ >>+ addr &= 0x7F; >>+ addr <<= 4; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; >>+ >>+ if (dev->major != 0x3) >>+ return; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF; >>+ if (pc_mode) >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000; >>+} >>+ >>+static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) >>+{ >>+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); >>+ return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; >>+} >>+ >>+static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, >>+ uint32_t vlevel) >>+{ >>+ unsigned int pmic_data = 0; >>+ >>+ /** >>+ * VCTL_PORT has to be 0, for PMIC_STS register to be updated. >>+ * Ensure that vctl_port is always set to 0. >>+ */ >>+ WARN_ON(dev->vctl_port); >>+ >>+ pmic_data |= vlevel; >>+ pmic_data |= (dev->vctl_port & 0x7) << 16; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data; >>+ >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3); >>+} >>+ >>+static inline uint32_t msm_spm_drv_get_num_pmic_data( >>+ struct msm_spm_driver_data *dev) >>+{ >>+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); >>+ mb(); >>+ return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7; >>+} >>+ >>+static inline uint32_t msm_spm_drv_get_sts_pmic_state( >>+ struct msm_spm_driver_data *dev) >>+{ >>+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); >>+ return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & >>+ 0x03; >>+} >>+ >>+uint32_t msm_spm_drv_get_sts_curr_pmic_data( >>+ struct msm_spm_driver_data *dev) >>+{ >>+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); >>+ return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; >>+} >>+ >>+inline int msm_spm_drv_set_spm_enable( >>+ struct msm_spm_driver_data *dev, bool enable) >>+{ >>+ uint32_t value = enable ? 0x01 : 0x00; >>+ >>+ if (!dev) >>+ return -EINVAL; >>+ >>+ if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; >>+ >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); >>+ wmb(); >>+ } >>+ return 0; >>+} >>+void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev) >>+{ >>+ int i; >>+ int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); >>+ >>+ if (!dev) { >>+ __WARN(); >>+ return; >>+ } >>+ >>+ for (i = 0; i < num_spm_entry; i++) { >>+ __raw_writel(dev->reg_seq_entry_shadow[i], >>+ dev->reg_base_addr >>+ + dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] >>+ + 4 * i); >>+ } >>+ mb(); >>+} >>+ >>+void dump_regs(struct msm_spm_driver_data *dev, int cpu) >>+{ >>+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); >>+ mb(); >>+ pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu, >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]); >>+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); >>+ mb(); >>+ pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu, >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]); >>+} >>+ >>+int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, >>+ uint8_t *cmd, uint32_t *offset) >>+{ >>+ uint32_t cmd_w; >>+ uint32_t offset_w = *offset / 4; >>+ uint8_t last_cmd; >>+ >>+ if (!cmd) >>+ return -EINVAL; >>+ >>+ while (1) { >>+ int i; >>+ >>+ cmd_w = 0; >>+ last_cmd = 0; >>+ cmd_w = dev->reg_seq_entry_shadow[offset_w]; >>+ >>+ for (i = (*offset % 4); i < 4; i++) { >>+ last_cmd = *(cmd++); >>+ cmd_w |= last_cmd << (i * 8); >>+ (*offset)++; >>+ if (last_cmd == 0x0f) >>+ break; >>+ } >>+ >>+ dev->reg_seq_entry_shadow[offset_w++] = cmd_w; >>+ if (last_cmd == 0x0f) >>+ break; >>+ } >>+ >>+ return 0; >>+} >>+ >>+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, >>+ uint32_t addr, bool pc_mode) >>+{ >>+ >>+ if (!dev) >>+ return -EINVAL; >>+ >>+ msm_spm_drv_set_start_addr(dev, addr, pc_mode); >>+ >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); >>+ wmb(); >>+ >>+ if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { >>+ int i; >>+ >>+ for (i = 0; i < MSM_SPM_REG_NR; i++) >>+ pr_info("%s: reg %02x = 0x%08x\n", __func__, >>+ dev->reg_offsets[i], dev->reg_shadow[i]); >>+ } >>+ msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); >>+ >>+ return 0; >>+} >>+ >>+int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel) >>+{ >>+ uint32_t timeout_us, new_level; >>+ >>+ if (!dev) >>+ return -EINVAL; >>+ >>+ if (!msm_spm_pmic_arb_present(dev)) >>+ return -ENOSYS; >>+ >>+ if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) >>+ pr_info("%s: requesting vlevel %#x\n", __func__, vlevel); >>+ >>+ /* Kick the state machine back to idle */ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1; >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST); >>+ >>+ msm_spm_drv_set_vctl2(dev, vlevel); >>+ >>+ timeout_us = dev->vctl_timeout_us; >>+ /* Confirm the voltage we set was what hardware sent */ >>+ do { >>+ new_level = msm_spm_drv_get_sts_curr_pmic_data(dev); >>+ if (new_level == vlevel) >>+ break; >>+ udelay(1); >>+ } while (--timeout_us); >>+ if (!timeout_us) { >>+ pr_info("Wrong level %#x\n", new_leve >>+ goto set_vdd_bail; >>+ } >>+ >>+ if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) >>+ pr_info("%s: done, remaining timeout %u us\n", >>+ __func__, timeout_us); >>+ >>+ return 0; >>+ >>+set_vdd_bail: >>+ pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n", >>+ __func__, vlevel, timeout_us, new_level); >>+ return -EIO; >>+} >>+ >>+static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev, >>+ enum msm_spm_pmic_port port) >>+{ >>+ int index = -1; >>+ >>+ switch (port) { >>+ case MSM_SPM_PMIC_VCTL_PORT: >>+ index = dev->vctl_port; >>+ break; >>+ case MSM_SPM_PMIC_PHASE_PORT: >>+ index = dev->phase_port; >>+ break; >>+ case MSM_SPM_PMIC_PFM_PORT: >>+ index = dev->pfm_port; >>+ break; >>+ default: >>+ break; >>+ } >>+ >>+ return index; >>+} >>+ >>+int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, >>+ enum msm_spm_pmic_port port, unsigned int data) >>+{ >>+ unsigned int pmic_data = 0; >>+ unsigned int timeout_us = 0; >>+ int index = 0; >>+ >>+ if (!msm_spm_pmic_arb_present(dev)) >>+ return -ENOSYS; >>+ >>+ index = msm_spm_drv_get_pmic_port(dev, port); >>+ if (index < 0) >>+ return -ENODEV; >>+ >>+ pmic_data |= data & 0xFF; >>+ pmic_data |= (index & 0x7) << 16; >>+ >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; >>+ dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; >>+ msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); >>+ mb(); >>+ >>+ timeout_us = dev->vctl_timeout_us; >>+ /** >>+ * Confirm the pmic data set was what hardware sent by >>+ * checking the PMIC FSM state. >>+ * We cannot use the sts_pmic_data and check it against >>+ * the value like we do fot set_vdd, since the PMIC_STS >>+ * is only updated for SAW_VCTL sent with port index 0. >>+ */ >>+ do { >>+ if (msm_spm_drv_get_sts_pmic_state(dev) == >>+ MSM_SPM_PMIC_STATE_IDLE) >>+ break; >>+ udelay(1); >>+ } while (--timeout_us); >>+ >>+ if (!timeout_us) { >>+ pr_err("%s: failed, remaining timeout %u us, data %d\n", >>+ __func__, timeout_us, data); >>+ return -EIO; >>+ } >>+ >>+ return 0; >>+} >>+ >>+void msm_spm_drv_reinit(struct msm_spm_driver_data *dev) >>+{ >>+ int i; >>+ >>+ msm_spm_drv_flush_seq_entry(dev); >>+ for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0 + num_pmic_data; i++) >>+ msm_spm_drv_flush_shadow(dev, i); >>+ >>+ mb(); > >Why are needed the mb() after calling the msm_spm_drv_flush_shadow >function ? > >>+ >>+ for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++) >>+ msm_spm_drv_load_shadow(dev, i); >>+} >>+ >>+int msm_spm_drv_init(struct msm_spm_driver_data *dev, >>+ struct msm_spm_platform_data *data) >>+{ >>+ int i; >>+ int num_spm_entry; >>+ bool found = false; >>+ >>+ BUG_ON(!dev || !data); >>+ >>+ dev->vctl_port = data->vctl_port; >>+ dev->phase_port = data->phase_port; >>+ dev->pfm_port = data->pfm_port; >>+ dev->reg_base_addr = data->reg_base_addr; >>+ memcpy(dev->reg_shadow, data->reg_init_values, >>+ sizeof(data->reg_init_values)); >>+ >>+ dev->vctl_timeout_us = data->vctl_timeout_us; >>+ >>+ for (i = 0; i < ARRAY_SIZE(saw2_info); i++) >>+ if (dev->major == saw2_info[i].major && >>+ dev->minor == saw2_info[i].minor) { >>+ pr_debug("%s: Version found\n", >>+ saw2_info[i].ver_name); >>+ dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr; >>+ found = true; >>+ break; >>+ } >>+ >>+ if (!found) { >>+ pr_err("%s: No SAW2 version found\n", __func__); >>+ BUG_ON(!found); >>+ } >>+ >>+ if (!num_pmic_data) >>+ num_pmic_data = msm_spm_drv_get_num_pmic_data(dev); >>+ >>+ num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); >>+ >>+ dev->reg_seq_entry_shadow = >>+ kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry, >>+ GFP_KERNEL); >>+ >>+ if (!dev->reg_seq_entry_shadow) >>+ return -ENOMEM; >>+ >>+ return 0; >>+} >>diff --git a/drivers/soc/qcom/spm_driver.h b/drivers/soc/qcom/spm_driver.h >>new file mode 100644 >>index 0000000..b306520 >>--- /dev/null >>+++ b/drivers/soc/qcom/spm_driver.h >>@@ -0,0 +1,116 @@ >>+/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. >>+ * >>+ * This program is free software; you can redistribute it and/or modify >>+ * it under the terms of the GNU General Public License version 2 and >>+ * only version 2 as published by the Free Software Foundation. >>+ * >>+ * This program is distributed in the hope that it will be useful, >>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of >>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>+ * GNU General Public License for more details. >>+ */ >>+#ifndef __QCOM_SPM_DRIVER_H >>+#define __QCOM_SPM_DRIVER_H >>+ >>+#include <soc/qcom/spm.h> >>+ >>+enum { >>+ MSM_SPM_REG_SAW2_CFG, >>+ MSM_SPM_REG_SAW2_AVS_CTL, >>+ MSM_SPM_REG_SAW2_AVS_HYSTERESIS, >>+ MSM_SPM_REG_SAW2_SPM_CTL, >>+ MSM_SPM_REG_SAW2_PMIC_DLY, >>+ MSM_SPM_REG_SAW2_AVS_LIMIT, >>+ MSM_SPM_REG_SAW2_AVS_DLY, >>+ MSM_SPM_REG_SAW2_SPM_DLY, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_0, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_1, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_2, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_3, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_4, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_5, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_6, >>+ MSM_SPM_REG_SAW2_PMIC_DATA_7, >>+ MSM_SPM_REG_SAW2_RST, >>+ >>+ MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST, >>+ >>+ MSM_SPM_REG_SAW2_ID, >>+ MSM_SPM_REG_SAW2_SECURE, >>+ MSM_SPM_REG_SAW2_STS0, >>+ MSM_SPM_REG_SAW2_STS1, >>+ MSM_SPM_REG_SAW2_STS2, >>+ MSM_SPM_REG_SAW2_VCTL, >>+ MSM_SPM_REG_SAW2_SEQ_ENTRY, >>+ MSM_SPM_REG_SAW2_SPM_STS, >>+ MSM_SPM_REG_SAW2_AVS_STS, >>+ MSM_SPM_REG_SAW2_PMIC_STS, >>+ MSM_SPM_REG_SAW2_VERSION, >>+ >>+ MSM_SPM_REG_NR, >>+}; >>+ >>+struct msm_spm_seq_entry { >>+ uint32_t mode; >>+ uint8_t *cmd; >>+ bool notify_rpm; >>+}; >>+ >>+struct msm_spm_platform_data { >>+ void __iomem *reg_base_addr; >>+ uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE]; >>+ >>+ uint32_t major; >>+ uint32_t minor; >>+ uint32_t vctl_port; >>+ uint32_t phase_port; >>+ uint32_t pfm_port; >>+ >>+ uint8_t awake_vlevel; >>+ uint32_t vctl_timeout_us; >>+ >>+ uint32_t num_modes; >>+ struct msm_spm_seq_entry *modes; >>+}; >>+ >>+enum msm_spm_pmic_port { >>+ MSM_SPM_PMIC_VCTL_PORT, >>+ MSM_SPM_PMIC_PHASE_PORT, >>+ MSM_SPM_PMIC_PFM_PORT, >>+}; >>+ >>+struct msm_spm_driver_data { >>+ uint32_t major; >>+ uint32_t minor; >>+ uint32_t vctl_port; >>+ uint32_t phase_port; >>+ uint32_t pfm_port; >>+ void __iomem *reg_base_addr; >>+ uint32_t vctl_timeout_us; >>+ uint32_t reg_shadow[MSM_SPM_REG_NR]; >>+ uint32_t *reg_seq_entry_shadow; >>+ uint32_t *reg_offsets; >>+}; >>+ >>+int msm_spm_drv_init(struct msm_spm_driver_data *dev, >>+ struct msm_spm_platform_data *data); >>+void msm_spm_drv_reinit(struct msm_spm_driver_data *dev); >>+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, >>+ uint32_t addr, bool pc_mode); >>+int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, >>+ unsigned int vlevel); >>+void dump_regs(struct msm_spm_driver_data *dev, int cpu); >>+uint32_t msm_spm_drv_get_sts_curr_pmic_data( >>+ struct msm_spm_driver_data *dev); >>+int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, >>+ uint8_t *cmd, uint32_t *offset); >>+void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev); >>+int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, >>+ bool enable); >>+int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, >>+ enum msm_spm_pmic_port port, unsigned int data); >>+ >>+void msm_spm_reinit(void); >>+int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs); >>+ >>+#endif /* __QCOM_SPM_DRIVER_H */ >>diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h >>new file mode 100644 >>index 0000000..f39e0c4 >>--- /dev/null >>+++ b/include/soc/qcom/spm.h >>@@ -0,0 +1,70 @@ >>+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved. >>+ * >>+ * This program is free software; you can redistribute it and/or modify >>+ * it under the terms of the GNU General Public License version 2 and >>+ * only version 2 as published by the Free Software Foundation. >>+ * >>+ * This program is distributed in the hope that it will be useful, >>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of >>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>+ * GNU General Public License for more details. >>+ */ >>+ >>+#ifndef __QCOM_SPM_H >>+#define __QCOM_SPM_H >>+ >>+enum { >>+ MSM_SPM_MODE_DISABLED, >>+ MSM_SPM_MODE_CLOCK_GATING, >>+ MSM_SPM_MODE_RETENTION, >>+ MSM_SPM_MODE_GDHS, >>+ MSM_SPM_MODE_POWER_COLLAPSE, >>+ MSM_SPM_MODE_NR >>+}; >>+ >>+struct msm_spm_device; >>+ >>+#if defined(CONFIG_QCOM_PM) >>+int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); >>+int msm_spm_probe_done(void); >>+int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel); >>+unsigned int msm_spm_get_vdd(unsigned int cpu); >>+int msm_spm_turn_on_cpu_rail(void __iomem *base, unsigned int val, int cpu); >>+struct msm_spm_device *msm_spm_get_device_by_name(const char *name); >>+int msm_spm_config_low_power_mode(struct msm_spm_device *dev, >>+ unsigned int mode, bool notify_rpm); >>+int msm_spm_device_init(void); >>+bool msm_spm_is_mode_avail(unsigned int mode); >>+void msm_spm_dump_regs(unsigned int cpu); >>+int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt); >>+int msm_spm_enable_fts_lpm(int cpu, uint32_t mode); >>+#else /* defined(CONFIG_QCOM_PM) */ >>+static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) >>+{ return -ENOSYS; } >>+static inline int msm_spm_probe_done(void) >>+{ return -ENOSYS; } >>+static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) >>+{ return -ENOSYS; } >>+static inline unsigned int msm_spm_get_vdd(unsigned int cpu) >>+{ return 0; } >>+static inline int msm_spm_turn_on_cpu_rail(void __iomem *base, >>+ unsigned int val, int cpu) >>+{ return -ENOSYS; } >>+static inline int msm_spm_device_init(void) >>+{ return -ENOSYS; } >>+static void msm_spm_dump_regs(unsigned int cpu) {} >>+static inline int msm_spm_config_low_power_mode(struct msm_spm_device *dev, >>+ unsigned int mode, bool notify_rpm) >>+{ return -ENODEV; } >>+static inline struct msm_spm_device *msm_spm_get_device_by_name( >>+ const char *name) >>+{ return NULL; } >>+static inline bool msm_spm_is_mode_avail(unsigned int mode) >>+{ return false; } >>+static inline int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt) >>+{ return -ENOSYS; } >>+static inline int msm_spm_enable_fts_lpm(int cpu, uint32_t mode) >>+{ return -ENOSYS; } >>+#endif /* defined (CONFIG_QCOM_PM) */ >>+ >>+#endif /* __QCOM_SPM_H */ >> > > >-- > <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs > >Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook | ><http://twitter.com/#!/linaroorg> Twitter | ><http://www.linaro.org/linaro-blog/> Blog > -- 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
On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote: > Qualcomm chipsets use an separate h/w block to control the logic around > the processor cores (cpu and L2). The SPM h/w block regulates power to > the cores and controls the power when the core enter low power modes. > > Each core has its own instance of SPM. The SPM has the following key > functions > - Configure the h/w dependencies when entering low power modes > - Wait for interrupt and wake up on interrupt > - Ensure the dependencies are ready before bringing the core out > of sleep > - Regulating voltage to the core, interfacing with the PMIC. > - Optimize power based on runtime recommendations. > > The driver identifies and configures the SPMs, by reading the nodes and > the register values from the devicetree. The SPMs need to be configured > to allow the processor to be idled in a low power state. > > Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org> > Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org> > Signed-off-by: Lina Iyer <lina.iyer@linaro.org> > --- > + <snip> > 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 <snip> > + 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; Initialization not needed for val. > + > + 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(); This may need a comment around it. > +} > + > +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); > + Please remove extra line. > + 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); Should not we release dev->reg_seq_entry_shadow allocated in msm_spm_drv_init? > +spm_failed_malloc: > + return ret; > +} > + > +/** > + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core <snip> > +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) > +{ > + unsigned int cpu; > + int ret = 0; Initialization not needed. > + > + 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__, <snip> > +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; Should we return -ENOMEM? ERR_PTR(-ENOMEM)? > + > + if (of_property_read_string(pdev->dev.of_node, key, &val)) { > + pr_err("%s(): Cannot find a required node key:%s\n", > + __func__, key); Should it be dev_err as we have access to pdev->dev? > + 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; Initialization not needed for ret. > + char *key = "qcom,cpu-vctl-list"; > + bool found = false; > + > + cpu_node = of_parse_phandle(node, key, idx++); > + while (cpu_node) { > + found = true; > + for_each_possible_cpu(c) { > + if (of_get_cpu_node(c, NULL) == cpu_node) > + cpumask_set_cpu(c, mask); > + } > + cpu_node = of_parse_phandle(node, key, idx++); > + }; > + > + if (found) > + return; > + > + key = "qcom,cpu-vctl-mask"; > + ret = of_property_read_u32(node, key, (u32 *) &vctl_mask); > + if (!ret) { > + for_each_set_bit(c, &vctl_mask, num_possible_cpus()) { > + cpumask_set_cpu(c, mask); > + } > + } > +} > + > +static int msm_spm_dev_probe(struct platform_device *pdev) > +{ > + int ret = 0; > + int cpu = 0; > + int i = 0; Initialization not needed for all three variables. > + 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; I might be misunderstanding something, but why cant we just return from here instead of jumping to 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; Should it return -EINVAL? > + 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", Can we use dev_err since pdev->dev is accessible? > + __func__); > + ret = -EADDRNOTAVAIL; ret = -ENOMEM? > + 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_info instead of pr_debug? > + 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); Should this be part of remove function instead of here as it is last initialization that is done in probe just before fail lable? If it is not needed the we can do away with this part of code from fail lable. > + } Are we missing a list_del like in remove function below? > + > + pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret); dev_ instead of pr_? > + > + 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); kfree(dev->modes) is missing. kfree(dev->reg_data->reg_seq_entry_shadow) is missing. > + > + 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) It should be static. > +{ > + 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_drv_init > + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, > + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, > + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, > + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, > + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, > +}; > + > +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = { > + [MSM_SPM_REG_SAW2_SECURE] = 0x00, > + [MSM_SPM_REG_SAW2_ID] = 0x04, > + [MSM_SPM_REG_SAW2_CFG] = 0x08, > + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, > + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, > + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, > + [MSM_SPM_REG_SAW2_RST] = 0x18, > + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, > + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, > + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, > + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, > + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, > + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, > + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, > + [MSM_SPM_REG_SAW2_STS2] = 0x38, > + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, > + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, > + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, > + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, > + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, > + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, > + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, > + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, > + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x400, > + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, > +}; > + > +static struct saw2_data saw2_info[] = { > + [0] = { > + "SAW2_v2.1", > + 2, > + 1, > + msm_spm_reg_offsets_saw2_v2_1, > + }, > + [1] = { > + "SAW2_v3.0", > + 3, > + 0, > + msm_spm_reg_offsets_saw2_v3_0, > + }, > +}; > + > +static uint32_t num_pmic_data; > + > +static inline uint32_t msm_spm_drv_get_num_spm_entry( > + struct msm_spm_driver_data *dev) > +{ > + return 32; > +} > + > +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, > + unsigned int reg_index) > +{ > + __raw_writel(dev->reg_shadow[reg_index], > + dev->reg_base_addr + dev->reg_offsets[reg_index]); > +} > + > +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, > + unsigned int reg_index) > +{ > + dev->reg_shadow[reg_index] = > + __raw_readl(dev->reg_base_addr + > + dev->reg_offsets[reg_index]); > +} > + > +static inline void msm_spm_drv_set_start_addr( > + struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode) > +{ > + addr &= 0x7F; > + addr <<= 4; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; > + > + if (dev->major != 0x3) > + return; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF; > + if (pc_mode) > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000; > +} > + > +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; > +} > + > +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, > + uint32_t vlevel) > +{ > + unsigned int pmic_data = 0; > + > + /** > + * VCTL_PORT has to be 0, for PMIC_STS register to be updated. > + * Ensure that vctl_port is always set to 0. > + */ > + WARN_ON(dev->vctl_port); > + > + pmic_data |= vlevel; > + pmic_data |= (dev->vctl_port & 0x7) << 16; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; > + > + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF; > + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data; > + > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); > + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3); > +} > + > +static inline uint32_t msm_spm_drv_get_num_pmic_data( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); > + mb(); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7; > +} > + > +static inline uint32_t msm_spm_drv_get_sts_pmic_state( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); > + return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & > + 0x03; > +} > + > +uint32_t msm_spm_drv_get_sts_curr_pmic_data( > + struct msm_spm_driver_data *dev) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); > + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; > +}msm_spm_drv_init > + > +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) { > + Please remove extra new line. > + 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; > + }msm_spm_drv_init > + > + for (i = 0; i < num_spm_entry; i++) { > + __raw_writel(dev->reg_seq_entry_shadow[i], > + dev->reg_base_addr > + + dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] > + + 4 * i); > + } > + mb(); > +} > + > +void dump_regs(struct msm_spm_driver_data *dev, int cpu) > +{ > + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); > + mb(); > + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu, > + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]); pr_info? > + 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]); pr_info? > +} > + > +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, > + uint8_t *cmd, uint32_t *offset) <snip> > + > +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, > + enum msm_spm_pmic_port port, unsigned int data)msm_spm_drv_init > +{ > + unsigned int pmic_data = 0; > + unsigned int timeout_us = 0; > + int index = 0; Initialization not needed for ind. > + > + if (!msm_spm_pmic_arb_present(dev)) > + return -ENOSYS; > + > + index = msm_spm_drv_get_pmic_port(dev, port); > + if (index < 0) <snip> > + 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); > + Please remove this extra new line here. > + 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 <snip> > -- 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
On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> 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@codeaurora.org> > Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org> > Signed-off-by: Lina Iyer <lina.iyer@linaro.org> > --- > .../devicetree/bindings/arm/msm/spm-v2.txt | 62 ++ > drivers/soc/qcom/Makefile | 2 + > drivers/soc/qcom/spm-devices.c | 703 +++++++++++++++++++++ > drivers/soc/qcom/spm.c | 482 ++++++++++++++ > drivers/soc/qcom/spm_driver.h | 116 ++++ > include/soc/qcom/spm.h | 70 ++ > 6 files changed, 1435 insertions(+) > create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt > create mode 100644 drivers/soc/qcom/spm-devices.c > create mode 100644 drivers/soc/qcom/spm.c > create mode 100644 drivers/soc/qcom/spm_driver.h > create mode 100644 include/soc/qcom/spm.h > > diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt > new file mode 100644 > index 0000000..3130f4b > --- /dev/null > +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt > @@ -0,0 +1,62 @@ > +* MSM Subsystem Power Manager (spm-v2) > + > +S4 generation of MSMs have SPM hardware blocks to control the Application > +Processor Sub-System power. These SPM blocks run individual state machine > +to determine what the core (L2 or Krait/Scorpion) would do when the WFI > +instruction is executed by the core. > + > +The devicetree representation of the SPM block should be: > + > +Required properties > + > +- compatible: Could be one of - > + "qcom,spm-v2.1" > + "qcom,spm-v3.0" > +- reg: The physical address and the size of the SPM's memory mapped registers > +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets > + that dont support CPU phandles the driver would support qcom,core-id. > + This field is required on only for SPMs that control the CPU. > +- qcom,saw2-cfg: SAW2 configuration register > +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM > + sequence > +- qcom,saw2-spm-ctl: The SPM control register > +- qcom,name: The name with which a SPM device is identified by the power > + management code. 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@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, Aug 14, 2014 at 06:31:09PM +0530, Pramod Gurav wrote: >On Wednesday 13 August 2014 01:13 AM, Lina Iyer wrote: >> Qualcomm chipsets use an separate h/w block to control the logic around >> the processor cores (cpu and L2). The SPM h/w block regulates power to >> the cores and controls the power when the core enter low power modes. >> >> Each core has its own instance of SPM. The SPM has the following key >> functions >> - Configure the h/w dependencies when entering low power modes >> - Wait for interrupt and wake up on interrupt >> - Ensure the dependencies are ready before bringing the core out >> of sleep >> - Regulating voltage to the core, interfacing with the PMIC. >> - Optimize power based on runtime recommendations. >> >> The driver identifies and configures the SPMs, by reading the nodes and >> the register values from the devicetree. The SPMs need to be configured >> to allow the processor to be idled in a low power state. >> >> Signed-off-by: Praveen Chidamabram <pchidamb@codeaurora.org> >> Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org> >> Signed-off-by: Lina Iyer <lina.iyer@linaro.org> >> --- >> + > ><snip> > >> 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 > ><snip> > >> + 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; >Initialization not needed for val. >> + >> + 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(); >This may need a comment around it. Sorry, my bad. I picked up the latest driver and this came with it. In my next version I pruned this code. >> +} >> + >> +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); >> + >Please remove extra line. >> + 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); >Should not we release dev->reg_seq_entry_shadow allocated in >msm_spm_drv_init? Good point. Will free it. >> +spm_failed_malloc: >> + return ret; >> +} >> + >> +/** >> + * msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core > ><snip> > >> +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) >> +{ >> + unsigned int cpu; >> + int ret = 0; >Initialization not needed. >> + >> + 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__, > ><snip> > >> +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; >Should we return -ENOMEM? ERR_PTR(-ENOMEM)? >> + >> + if (of_property_read_string(pdev->dev.of_node, key, &val)) { >> + pr_err("%s(): Cannot find a required node key:%s\n", >> + __func__, key); >Should it be dev_err as we have access to pdev->dev? Yes. >> + 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; >Initialization not needed for ret. >> + char *key = "qcom,cpu-vctl-list"; >> + bool found = false; >> + >> + cpu_node = of_parse_phandle(node, key, idx++); >> + while (cpu_node) { >> + found = true; >> + for_each_possible_cpu(c) { >> + if (of_get_cpu_node(c, NULL) == cpu_node) >> + cpumask_set_cpu(c, mask); >> + } >> + cpu_node = of_parse_phandle(node, key, idx++); >> + }; >> + >> + if (found) >> + return; >> + >> + key = "qcom,cpu-vctl-mask"; >> + ret = of_property_read_u32(node, key, (u32 *) &vctl_mask); >> + if (!ret) { >> + for_each_set_bit(c, &vctl_mask, num_possible_cpus()) { >> + cpumask_set_cpu(c, mask); >> + } >> + } >> +} >> + >> +static int msm_spm_dev_probe(struct platform_device *pdev) >> +{ >> + int ret = 0; >> + int cpu = 0; >> + int i = 0; >Initialization not needed for all three variables. >> + 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; >I might be misunderstanding something, but why cant we just return from >here instead of jumping to 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; >Should it return -EINVAL? >> + 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", >Can we use dev_err since pdev->dev is accessible? >> + __func__); >> + ret = -EADDRNOTAVAIL; >ret = -ENOMEM? >> + 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_info instead of pr_debug? ok >> + 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); >Should this be part of remove function instead of here as it is last >initialization that is done in probe just before fail lable? If it is >not needed the we can do away with this part of code from fail lable. >> + } >Are we missing a list_del like in remove function below? Yes we are. >> + >> + pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret); >dev_ instead of pr_? >> + >> + 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); >kfree(dev->modes) is missing. >kfree(dev->reg_data->reg_seq_entry_shadow) is missing. >> + >> + 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) >It should be static. Yes. >> +{ >> + 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_drv_init >> + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, >> + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, >> + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, >> +}; >> + >> +static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = { >> + [MSM_SPM_REG_SAW2_SECURE] = 0x00, >> + [MSM_SPM_REG_SAW2_ID] = 0x04, >> + [MSM_SPM_REG_SAW2_CFG] = 0x08, >> + [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C, >> + [MSM_SPM_REG_SAW2_AVS_STS] = 0x10, >> + [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14, >> + [MSM_SPM_REG_SAW2_RST] = 0x18, >> + [MSM_SPM_REG_SAW2_VCTL] = 0x1C, >> + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20, >> + [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24, >> + [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28, >> + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C, >> + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30, >> + [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34, >> + [MSM_SPM_REG_SAW2_STS2] = 0x38, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58, >> + [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C, >> + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x400, >> + [MSM_SPM_REG_SAW2_VERSION] = 0xFD0, >> +}; >> + >> +static struct saw2_data saw2_info[] = { >> + [0] = { >> + "SAW2_v2.1", >> + 2, >> + 1, >> + msm_spm_reg_offsets_saw2_v2_1, >> + }, >> + [1] = { >> + "SAW2_v3.0", >> + 3, >> + 0, >> + msm_spm_reg_offsets_saw2_v3_0, >> + }, >> +}; >> + >> +static uint32_t num_pmic_data; >> + >> +static inline uint32_t msm_spm_drv_get_num_spm_entry( >> + struct msm_spm_driver_data *dev) >> +{ >> + return 32; >> +} >> + >> +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, >> + unsigned int reg_index) >> +{ >> + __raw_writel(dev->reg_shadow[reg_index], >> + dev->reg_base_addr + dev->reg_offsets[reg_index]); >> +} >> + >> +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, >> + unsigned int reg_index) >> +{ >> + dev->reg_shadow[reg_index] = >> + __raw_readl(dev->reg_base_addr + >> + dev->reg_offsets[reg_index]); >> +} >> + >> +static inline void msm_spm_drv_set_start_addr( >> + struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode) >> +{ >> + addr &= 0x7F; >> + addr <<= 4; >> + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; >> + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; >> + >> + if (dev->major != 0x3) >> + return; >> + >> + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF; >> + if (pc_mode) >> + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000; >> +} >> + >> +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) >> +{ >> + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); >> + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; >> +} >> + >> +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, >> + uint32_t vlevel) >> +{ >> + unsigned int pmic_data = 0; >> + >> + /** >> + * VCTL_PORT has to be 0, for PMIC_STS register to be updated. >> + * Ensure that vctl_port is always set to 0. >> + */ >> + WARN_ON(dev->vctl_port); >> + >> + pmic_data |= vlevel; >> + pmic_data |= (dev->vctl_port & 0x7) << 16; >> + >> + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; >> + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; >> + >> + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF; >> + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data; >> + >> + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); >> + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3); >> +} >> + >> +static inline uint32_t msm_spm_drv_get_num_pmic_data( >> + struct msm_spm_driver_data *dev) >> +{ >> + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); >> + mb(); >> + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7; >> +} >> + >> +static inline uint32_t msm_spm_drv_get_sts_pmic_state( >> + struct msm_spm_driver_data *dev) >> +{ >> + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); >> + return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & >> + 0x03; >> +} >> + >> +uint32_t msm_spm_drv_get_sts_curr_pmic_data( >> + struct msm_spm_driver_data *dev) >> +{ >> + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); >> + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; >> +}msm_spm_drv_init >> + >> +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) { >> + >Please remove extra new line. >> + 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; >> + }msm_spm_drv_init >> + >> + for (i = 0; i < num_spm_entry; i++) { >> + __raw_writel(dev->reg_seq_entry_shadow[i], >> + dev->reg_base_addr >> + + dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] >> + + 4 * i); >> + } >> + mb(); >> +} >> + >> +void dump_regs(struct msm_spm_driver_data *dev, int cpu) >> +{ >> + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); >> + mb(); >> + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu, >> + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]); >pr_info? >> + 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]); >pr_info? >> +} >> + >> +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, >> + uint8_t *cmd, uint32_t *offset) > ><snip> >> + >> +int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev, >> + enum msm_spm_pmic_port port, unsigned int data)msm_spm_drv_init >> +{ >> + unsigned int pmic_data = 0; >> + unsigned int timeout_us = 0; >> + int index = 0; >Initialization not needed for ind. >> + >> + if (!msm_spm_pmic_arb_present(dev)) >> + return -ENOSYS; >> + >> + index = msm_spm_drv_get_pmic_port(dev, port); >> + if (index < 0) > ><snip> > >> + 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); >> + >Please remove this extra new line here. >> + 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 > ><snip> > >> -- 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
On Thu, Aug 14, 2014 at 10:16:15AM -0500, Kumar Gala wrote: > >On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> 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@codeaurora.org> >> Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org> >> Signed-off-by: Lina Iyer <lina.iyer@linaro.org> >> --- >> .../devicetree/bindings/arm/msm/spm-v2.txt | 62 ++ >> drivers/soc/qcom/Makefile | 2 + >> drivers/soc/qcom/spm-devices.c | 703 +++++++++++++++++++++ >> drivers/soc/qcom/spm.c | 482 ++++++++++++++ >> drivers/soc/qcom/spm_driver.h | 116 ++++ >> include/soc/qcom/spm.h | 70 ++ >> 6 files changed, 1435 insertions(+) >> create mode 100644 Documentation/devicetree/bindings/arm/msm/spm-v2.txt >> create mode 100644 drivers/soc/qcom/spm-devices.c >> create mode 100644 drivers/soc/qcom/spm.c >> create mode 100644 drivers/soc/qcom/spm_driver.h >> create mode 100644 include/soc/qcom/spm.h >> >> diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt >> new file mode 100644 >> index 0000000..3130f4b >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt >> @@ -0,0 +1,62 @@ >> +* MSM Subsystem Power Manager (spm-v2) >> + >> +S4 generation of MSMs have SPM hardware blocks to control the Application >> +Processor Sub-System power. These SPM blocks run individual state machine >> +to determine what the core (L2 or Krait/Scorpion) would do when the WFI >> +instruction is executed by the core. >> + >> +The devicetree representation of the SPM block should be: >> + >> +Required properties >> + >> +- compatible: Could be one of - >> + "qcom,spm-v2.1" >> + "qcom,spm-v3.0" >> +- reg: The physical address and the size of the SPM's memory mapped registers >> +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets >> + that dont support CPU phandles the driver would support qcom,core-id. >> + This field is required on only for SPMs that control the CPU. >> +- qcom,saw2-cfg: SAW2 configuration register >> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM >> + sequence >> +- qcom,saw2-spm-ctl: The SPM control register >> +- qcom,name: The name with which a SPM device is identified by the power >> + management code. > >Still not sure about this, wondering why we can’t 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 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? > 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 = 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. Done. > >> +} >> + >> +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. > Isnt that an hack ? >> + >> +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? > 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] = >> + __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() 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 = *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. 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 use 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, 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@vger.kernel.org >> 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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
>>> 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. >> > Isnt that an hack ? Why would it be a hack, if the only difference in the driver can be reduced down to 5 lines of code, versus what is currently there I don’t see that as a hack at all. [ snip ] >>> 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. > 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 use > until msm-pm.c comes along. Yeah, is probably best to at least introduce CONFIG_QCOM_PM with this patch so the driver can be built, even if it doesn’t really do much but initialize. [ snip ] - k
On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote: > > 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 Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT. > +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM > + sequence > +- qcom,saw2-spm-ctl: The SPM control register Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”? Also, I’m unclear why would we have a case that spm would be disabled? > +- qcom,name: The name with which a SPM device is identified by the power > + management code. > + > +Optional properties > + > +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS > + (Fast Transient Switch) index to send the PMIC data to > +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing > + voltage > +- qcom,phase-port: The PVC port used for changing the number of phases > +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes > +- qcom,saw2-spm-cmd-wfi: The WFI command sequence > +- qcom,saw2-spm-cmd-ret: The Retention command sequence > +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence > +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS > + proc won't inform the RPM. > +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may > + turn off other SoC components. > +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command > + sequence. This sequence will retain the memory but turn off the logic. > +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device > + can control. > +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to > + change after sending the voltage command to the PMIC. > +- > +Example: > + qcom,spm@f9089000 { > + compatible = "qcom,spm-v2"; > + #address-cells = <1>; > + #size-cells = <1>; > + reg = <0xf9089000 0x1000>; > + qcom,cpu = <&CPU0>; > + qcom,saw2-cfg = <0x1>; > + qcom,saw2-spm-dly= <0x20000400>; > + qcom,saw2-spm-ctl = <0x1>; > + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; > + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 > + a0 b0 03 68 70 3b 92 a0 b0 > + 82 2b 50 10 30 02 22 30 0f]; > + };
On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote: > >On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote: > >> >> 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 > >Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT. > I am pruning them off in the next revision of the patch. >> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM >> + sequence >> +- qcom,saw2-spm-ctl: The SPM control register > >Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”? > >Also, I’m unclear why would we have a case that spm would be disabled? > Much of these registers names make it easier for developers and debuggers to relate it to the hardware spec. Choosing different names here though might make it readable would convolute their efforts. >> +- qcom,name: The name with which a SPM device is identified by the power >> + management code. >> + >> +Optional properties >> + >> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS >> + (Fast Transient Switch) index to send the PMIC data to >> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing >> + voltage >> +- qcom,phase-port: The PVC port used for changing the number of phases >> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes >> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence >> +- qcom,saw2-spm-cmd-ret: The Retention command sequence >> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence >> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS >> + proc won't inform the RPM. >> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may >> + turn off other SoC components. >> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command >> + sequence. This sequence will retain the memory but turn off the logic. >> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device >> + can control. >> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to >> + change after sending the voltage command to the PMIC. >> +- >> +Example: >> + qcom,spm@f9089000 { >> + compatible = "qcom,spm-v2"; >> + #address-cells = <1>; >> + #size-cells = <1>; >> + reg = <0xf9089000 0x1000>; >> + qcom,cpu = <&CPU0>; >> + qcom,saw2-cfg = <0x1>; >> + qcom,saw2-spm-dly= <0x20000400>; >> + qcom,saw2-spm-ctl = <0x1>; >> + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; >> + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 >> + a0 b0 03 68 70 3b 92 a0 b0 >> + 82 2b 50 10 30 02 22 30 0f]; >> + }; > >-- >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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote: > On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote: >> >> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote: >> >>> >>> 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 >> >> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT. >> > I am pruning them off in the next revision of the patch. >>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM >>> + sequence >>> +- qcom,saw2-spm-ctl: The SPM control register >> >> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”? >> >> Also, I’m unclear why would we have a case that spm would be disabled? >> > Much of these registers names make it easier for developers and > debuggers to relate it to the hardware spec. Choosing different names > here though might make it readable would convolute their efforts. The point is to move away from just dumping a register value directly from DT into the device. This is pretty bad form. The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go. > >>> +- qcom,name: The name with which a SPM device is identified by the power >>> + management code. >>> + >>> +Optional properties >>> + >>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS >>> + (Fast Transient Switch) index to send the PMIC data to >>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing >>> + voltage >>> +- qcom,phase-port: The PVC port used for changing the number of phases >>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes >>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence >>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence >>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence >>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS >>> + proc won't inform the RPM. >>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may >>> + turn off other SoC components. >>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command >>> + sequence. This sequence will retain the memory but turn off the logic. >>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device >>> + can control. >>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to >>> + change after sending the voltage command to the PMIC. >>> +- >>> +Example: >>> + qcom,spm@f9089000 { >>> + compatible = "qcom,spm-v2"; >>> + #address-cells = <1>; >>> + #size-cells = <1>; >>> + reg = <0xf9089000 0x1000>; >>> + qcom,cpu = <&CPU0>; >>> + qcom,saw2-cfg = <0x1>; >>> + qcom,saw2-spm-dly= <0x20000400>; >>> + qcom,saw2-spm-ctl = <0x1>; >>> + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; >>> + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 >>> + a0 b0 03 68 70 3b 92 a0 b0 >>> + 82 2b 50 10 30 02 22 30 0f]; >>> + }; >> >> -- >> Employee of Qualcomm Innovation Center, Inc. >> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation >> - k
On Thu, Aug 14, 2014 at 11:41:39AM -0500, Kumar Gala wrote: > >On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote: > >> On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote: >>> >>> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote: >>> >>>> >>>> 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 >>> >>> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT. >>> >> I am pruning them off in the next revision of the patch. >>>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM >>>> + sequence >>>> +- qcom,saw2-spm-ctl: The SPM control register >>> >>> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”? >>> >>> Also, I’m unclear why would we have a case that spm would be disabled? SPM would mostly be disabled for debug reasons or if there was a version of the hardware tht needed hardware to be disabled. In general, it wouldnt be. >>> >> Much of these registers names make it easier for developers and >> debuggers to relate it to the hardware spec. Choosing different names >> here though might make it readable would convolute their efforts. > >The point is to move away from just dumping a register value directly from DT into the device. This is pretty bad form. The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go. > Hmm. I see. Let me if I can address that.. There may be some registers where I may not have such a luxury, will give it a try. >> >>>> +- qcom,name: The name with which a SPM device is identified by the power >>>> + management code. >>>> + >>>> +Optional properties >>>> + >>>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS >>>> + (Fast Transient Switch) index to send the PMIC data to >>>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing >>>> + voltage >>>> +- qcom,phase-port: The PVC port used for changing the number of phases >>>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes >>>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence >>>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence >>>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence >>>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS >>>> + proc won't inform the RPM. >>>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may >>>> + turn off other SoC components. >>>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command >>>> + sequence. This sequence will retain the memory but turn off the logic. >>>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device >>>> + can control. >>>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to >>>> + change after sending the voltage command to the PMIC. >>>> +- >>>> +Example: >>>> + qcom,spm@f9089000 { >>>> + compatible = "qcom,spm-v2"; >>>> + #address-cells = <1>; >>>> + #size-cells = <1>; >>>> + reg = <0xf9089000 0x1000>; >>>> + qcom,cpu = <&CPU0>; >>>> + qcom,saw2-cfg = <0x1>; >>>> + qcom,saw2-spm-dly= <0x20000400>; >>>> + qcom,saw2-spm-ctl = <0x1>; >>>> + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; >>>> + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 >>>> + a0 b0 03 68 70 3b 92 a0 b0 >>>> + 82 2b 50 10 30 02 22 30 0f]; >>>> + }; >>> >>> -- >>> Employee of Qualcomm Innovation Center, Inc. >>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation >>> > >- k > >-- >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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Aug 14, 2014, at 11:18 PM, Lina Iyer <lina.iyer@linaro.org> wrote: > On Thu, Aug 14, 2014 at 11:41:39AM -0500, Kumar Gala wrote: >> >> On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote: >> >>> On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote: >>>> >>>> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote: >>>> >>>>> >>>>> 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 >>>> >>>> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT. >>>> >>> I am pruning them off in the next revision of the patch. >>>>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM >>>>> + sequence >>>>> +- qcom,saw2-spm-ctl: The SPM control register >>>> >>>> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”? >>>> >>>> Also, I’m unclear why would we have a case that spm would be disabled? > SPM would mostly be disabled for debug reasons or if there was a version > of the hardware tht needed hardware to be disabled. In general, it > wouldnt be. Why would we not do that via the “status” field? >>>> >>> Much of these registers names make it easier for developers and >>> debuggers to relate it to the hardware spec. Choosing different names >>> here though might make it readable would convolute their efforts. >> >> The point is to move away from just dumping a register value directly from DT into the device. This is pretty bad form. The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go. >> > Hmm. I see. Let me if I can address that.. There may be some registers > where I may not have such a luxury, will give it a try. Lets see, some registers are possibly ok, so lets try as much as possibly and go from there. > >>> >>>>> +- qcom,name: The name with which a SPM device is identified by the power >>>>> + management code. >>>>> + >>>>> +Optional properties >>>>> + >>>>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS >>>>> + (Fast Transient Switch) index to send the PMIC data to >>>>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing >>>>> + voltage >>>>> +- qcom,phase-port: The PVC port used for changing the number of phases >>>>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes >>>>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence >>>>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence >>>>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence >>>>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS >>>>> + proc won't inform the RPM. >>>>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may >>>>> + turn off other SoC components. >>>>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command >>>>> + sequence. This sequence will retain the memory but turn off the logic. >>>>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device >>>>> + can control. >>>>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to >>>>> + change after sending the voltage command to the PMIC. >>>>> +- >>>>> +Example: >>>>> + qcom,spm@f9089000 { >>>>> + compatible = "qcom,spm-v2"; >>>>> + #address-cells = <1>; >>>>> + #size-cells = <1>; >>>>> + reg = <0xf9089000 0x1000>; >>>>> + qcom,cpu = <&CPU0>; >>>>> + qcom,saw2-cfg = <0x1>; >>>>> + qcom,saw2-spm-dly= <0x20000400>; >>>>> + qcom,saw2-spm-ctl = <0x1>; >>>>> + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; >>>>> + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 >>>>> + a0 b0 03 68 70 3b 92 a0 b0 >>>>> + 82 2b 50 10 30 02 22 30 0f]; >>>>> + }; >>>> >>>> -- >>>> Employee of Qualcomm Innovation Center, Inc. >>>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation >>>> >> >> - k >> >> -- >> 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@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html
On Fri, Aug 15, 2014 at 08:42:17AM -0500, Kumar Gala wrote: > >On Aug 14, 2014, at 11:18 PM, Lina Iyer <lina.iyer@linaro.org> wrote: > >> On Thu, Aug 14, 2014 at 11:41:39AM -0500, Kumar Gala wrote: >>> >>> On Aug 14, 2014, at 11:18 AM, Lina Iyer <lina.iyer@linaro.org> wrote: >>> >>>> On Thu, Aug 14, 2014 at 11:09:48AM -0500, Kumar Gala wrote: >>>>> >>>>> On Aug 12, 2014, at 2:43 PM, Lina Iyer <lina.iyer@linaro.org> wrote: >>>>> >>>>>> >>>>>> 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 >>>>> >>>>> Can we change this to qcom,saw2-clk-div as that is what is really getting set, I know there are a few other fields in the saw2-cfg register, but I’m pretty sure we arent ever really setting those from DT. >>>>> >>>> I am pruning them off in the next revision of the patch. >>>>>> +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM >>>>>> + sequence >>>>>> +- qcom,saw2-spm-ctl: The SPM control register >>>>> >>>>> Can we describe this as “spm-enable”, “spm-inhibit-start-address”, “spm-wakeup-cfg”? >>>>> >>>>> Also, I’m unclear why would we have a case that spm would be disabled? >> SPM would mostly be disabled for debug reasons or if there was a version >> of the hardware tht needed hardware to be disabled. In general, it >> wouldnt be. > >Why would we not do that via the “status” field? > We could do that instead too. >>>>> >>>> Much of these registers names make it easier for developers and >>>> debuggers to relate it to the hardware spec. Choosing different names >>>> here though might make it readable would convolute their efforts. >>> >>> The point is to move away from just dumping a register value directly from DT into the device. This is pretty bad form. The names can relate to the register, etc, its just the fields that are really being used/set was the direction I was suggesting we go. >>> >> Hmm. I see. Let me if I can address that.. There may be some registers >> where I may not have such a luxury, will give it a try. > >Lets see, some registers are possibly ok, so lets try as much as possibly and go from there. Ok. > >> >>>> >>>>>> +- qcom,name: The name with which a SPM device is identified by the power >>>>>> + management code. >>>>>> + >>>>>> +Optional properties >>>>>> + >>>>>> +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS >>>>>> + (Fast Transient Switch) index to send the PMIC data to >>>>>> +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing >>>>>> + voltage >>>>>> +- qcom,phase-port: The PVC port used for changing the number of phases >>>>>> +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes >>>>>> +- qcom,saw2-spm-cmd-wfi: The WFI command sequence >>>>>> +- qcom,saw2-spm-cmd-ret: The Retention command sequence >>>>>> +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence >>>>>> +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS >>>>>> + proc won't inform the RPM. >>>>>> +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may >>>>>> + turn off other SoC components. >>>>>> +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command >>>>>> + sequence. This sequence will retain the memory but turn off the logic. >>>>>> +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device >>>>>> + can control. >>>>>> +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to >>>>>> + change after sending the voltage command to the PMIC. >>>>>> +- >>>>>> +Example: >>>>>> + qcom,spm@f9089000 { >>>>>> + compatible = "qcom,spm-v2"; >>>>>> + #address-cells = <1>; >>>>>> + #size-cells = <1>; >>>>>> + reg = <0xf9089000 0x1000>; >>>>>> + qcom,cpu = <&CPU0>; >>>>>> + qcom,saw2-cfg = <0x1>; >>>>>> + qcom,saw2-spm-dly= <0x20000400>; >>>>>> + qcom,saw2-spm-ctl = <0x1>; >>>>>> + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; >>>>>> + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 >>>>>> + a0 b0 03 68 70 3b 92 a0 b0 >>>>>> + 82 2b 50 10 30 02 22 30 0f]; >>>>>> + }; >>>>> >>>>> -- >>>>> Employee of Qualcomm Innovation Center, Inc. >>>>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation >>>>> >>> >>> - k >>> >>> -- >>> 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@vger.kernel.org >> 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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/Documentation/devicetree/bindings/arm/msm/spm-v2.txt b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt new file mode 100644 index 0000000..3130f4b --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/spm-v2.txt @@ -0,0 +1,62 @@ +* MSM Subsystem Power Manager (spm-v2) + +S4 generation of MSMs have SPM hardware blocks to control the Application +Processor Sub-System power. These SPM blocks run individual state machine +to determine what the core (L2 or Krait/Scorpion) would do when the WFI +instruction is executed by the core. + +The devicetree representation of the SPM block should be: + +Required properties + +- compatible: Could be one of - + "qcom,spm-v2.1" + "qcom,spm-v3.0" +- reg: The physical address and the size of the SPM's memory mapped registers +- qcom,cpu: phandle for the CPU that the SPM block is attached to. On targets + that dont support CPU phandles the driver would support qcom,core-id. + This field is required on only for SPMs that control the CPU. +- qcom,saw2-cfg: SAW2 configuration register +- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM + sequence +- qcom,saw2-spm-ctl: The SPM control register +- qcom,name: The name with which a SPM device is identified by the power + management code. + +Optional properties + +- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS + (Fast Transient Switch) index to send the PMIC data to +- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing + voltage +- qcom,phase-port: The PVC port used for changing the number of phases +- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes +- qcom,saw2-spm-cmd-wfi: The WFI command sequence +- qcom,saw2-spm-cmd-ret: The Retention command sequence +- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence +- qcom,saw2-spm-cmd-pc-no-rpm: The Power Collapse command sequence where APPS + proc won't inform the RPM. +- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may + turn off other SoC components. +- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command + sequence. This sequence will retain the memory but turn off the logic. +- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device + can control. +- qcom,vctl-timeout-us: The timeout value in microseconds to wait for voltage to + change after sending the voltage command to the PMIC. +- +Example: + qcom,spm@f9089000 { + compatible = "qcom,spm-v2"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0xf9089000 0x1000>; + qcom,cpu = <&CPU0>; + qcom,saw2-cfg = <0x1>; + qcom,saw2-spm-dly= <0x20000400>; + qcom,saw2-spm-ctl = <0x1>; + qcom,saw2-spm-cmd-wfi = [03 0b 0f]; + qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92 + a0 b0 03 68 70 3b 92 a0 b0 + 82 2b 50 10 30 02 22 30 0f]; + }; diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 70d52ed..d7ae93b 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,3 +1,5 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o +obj-$(CONFIG_QCOM_PM) += spm-devices.o spm.o + CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c new file mode 100644 index 0000000..567e9f9 --- /dev/null +++ b/drivers/soc/qcom/spm-devices.c @@ -0,0 +1,703 @@ +/* Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/err.h> + +#include <soc/qcom/spm.h> + +#include "spm_driver.h" + +#define VDD_DEFAULT 0xDEADF00D + +struct msm_spm_power_modes { + uint32_t mode; + bool notify_rpm; + uint32_t start_addr; +}; + +struct msm_spm_device { + struct list_head list; + bool initialized; + const char *name; + struct msm_spm_driver_data reg_data; + struct msm_spm_power_modes *modes; + uint32_t num_modes; + uint32_t cpu_vdd; + struct cpumask mask; + void __iomem *q2s_reg; +}; + +struct msm_spm_vdd_info { + struct msm_spm_device *vctl_dev; + uint32_t vlevel; + int err; +}; + +static LIST_HEAD(spm_list); +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device); +static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device); + +static void msm_spm_smp_set_vdd(void *data) +{ + struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data; + struct msm_spm_device *dev = info->vctl_dev; + + dev->cpu_vdd = info->vlevel; + info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel); +} + +/** + * msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2 + * probe. + * Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER. + * if probe failed, then return the err number for that failure. + */ +int msm_spm_probe_done(void) +{ + struct msm_spm_device *dev; + int cpu; + int ret = 0; + + for_each_possible_cpu(cpu) { + dev = per_cpu(cpu_vctl_device, cpu); + if (!dev) + return -EPROBE_DEFER; + + ret = IS_ERR(dev); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(msm_spm_probe_done); + +void msm_spm_dump_regs(unsigned int cpu) +{ + dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu); +} + +/** + * msm_spm_set_vdd(): Set core voltage + * @cpu: core id + * @vlevel: Encoded PMIC data. + * + * Return: 0 on success or -(ERRNO) on failure. + */ +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) +{ + struct msm_spm_vdd_info info; + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + int ret; + + if (!dev) + return -EPROBE_DEFER; + + ret = IS_ERR(dev); + if (ret) + return ret; + + info.vctl_dev = dev; + info.vlevel = vlevel; + + ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info, + true); + if (ret) + return ret; + + 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); + } + } +} + +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, +}; + +static struct saw2_data saw2_info[] = { + [0] = { + "SAW2_v2.1", + 2, + 1, + msm_spm_reg_offsets_saw2_v2_1, + }, + [1] = { + "SAW2_v3.0", + 3, + 0, + msm_spm_reg_offsets_saw2_v3_0, + }, +}; + +static uint32_t num_pmic_data; + +static inline uint32_t msm_spm_drv_get_num_spm_entry( + struct msm_spm_driver_data *dev) +{ + return 32; +} + +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + __raw_writel(dev->reg_shadow[reg_index], + dev->reg_base_addr + dev->reg_offsets[reg_index]); +} + +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + dev->reg_shadow[reg_index] = + __raw_readl(dev->reg_base_addr + + dev->reg_offsets[reg_index]); +} + +static inline void msm_spm_drv_set_start_addr( + struct msm_spm_driver_data *dev, uint32_t addr, bool pc_mode) +{ + addr &= 0x7F; + addr <<= 4; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; + + if (dev->major != 0x3) + return; + + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFEFFFF; + if (pc_mode) + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= 0x00010000; +} + +static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 2) & 0x1; +} + +static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev, + uint32_t vlevel) +{ + unsigned int pmic_data = 0; + + /** + * VCTL_PORT has to be 0, for PMIC_STS register to be updated. + * Ensure that vctl_port is always set to 0. + */ + WARN_ON(dev->vctl_port); + + pmic_data |= vlevel; + pmic_data |= (dev->vctl_port & 0x7) << 16; + + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= pmic_data; + + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] &= ~0x700FF; + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_3] |= pmic_data; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_3); +} + +static inline uint32_t msm_spm_drv_get_num_pmic_data( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_ID); + mb(); + return (dev->reg_shadow[MSM_SPM_REG_SAW2_ID] >> 4) & 0x7; +} + +static inline uint32_t msm_spm_drv_get_sts_pmic_state( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); + return (dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] >> 16) & + 0x03; +} + +uint32_t msm_spm_drv_get_sts_curr_pmic_data( + struct msm_spm_driver_data *dev) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_PMIC_STS); + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_STS] & 0xFF; +} + +inline int msm_spm_drv_set_spm_enable( + struct msm_spm_driver_data *dev, bool enable) +{ + uint32_t value = enable ? 0x01 : 0x00; + + if (!dev) + return -EINVAL; + + if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { + + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + wmb(); + } + return 0; +} +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev) +{ + int i; + int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + if (!dev) { + __WARN(); + return; + } + + for (i = 0; i < num_spm_entry; i++) { + __raw_writel(dev->reg_seq_entry_shadow[i], + dev->reg_base_addr + + dev->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] + + 4 * i); + } + mb(); +} + +void dump_regs(struct msm_spm_driver_data *dev, int cpu) +{ + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); + mb(); + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_STS: 0x%x\n", cpu, + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_STS]); + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + mb(); + pr_err("CPU%d: spm register MSM_SPM_REG_SAW2_SPM_CTL: 0x%x\n", cpu, + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL]); +} + +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, + uint8_t *cmd, uint32_t *offset) +{ + uint32_t cmd_w; + uint32_t offset_w = *offset / 4; + uint8_t last_cmd; + + if (!cmd) + return -EINVAL; + + while (1) { + int i; + + cmd_w = 0; + last_cmd = 0; + cmd_w = dev->reg_seq_entry_shadow[offset_w]; + + for (i = (*offset % 4); i < 4; i++) { + last_cmd = *(cmd++); + cmd_w |= last_cmd << (i * 8); + (*offset)++; + if (last_cmd == 0x0f) + break; + } + + dev->reg_seq_entry_shadow[offset_w++] = cmd_w; + if (last_cmd == 0x0f) + break; + } + + return 0; +} + +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t addr, bool pc_mode) +{ + + if (!dev) + return -EINVAL; + + msm_spm_drv_set_start_addr(dev, addr, pc_mode); + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + wmb(); + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { + int i; + + for (i = 0; i < MSM_SPM_REG_NR; i++) + pr_info("%s: reg %02x = 0x%08x\n", __func__, + dev->reg_offsets[i], dev->reg_shadow[i]); + } + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_SPM_STS); + + return 0; +} + +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel) +{ + uint32_t timeout_us, new_level; + + if (!dev) + return -EINVAL; + + if (!msm_spm_pmic_arb_present(dev)) + return -ENOSYS; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: requesting vlevel %#x\n", __func__, vlevel); + + /* Kick the state machine back to idle */ + dev->reg_shadow[MSM_SPM_REG_SAW2_RST] = 1; + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_RST); + + msm_spm_drv_set_vctl2(dev, vlevel); + + timeout_us = dev->vctl_timeout_us; + /* Confirm the voltage we set was what hardware sent */ + do { + new_level = msm_spm_drv_get_sts_curr_pmic_data(dev); + if (new_level == vlevel) + break; + udelay(1); + } while (--timeout_us); + if (!timeout_us) { + pr_info("Wrong level %#x\n", new_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) +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 */