diff options
author | Abhijeet Dharmapurikar <adharmap@codeaurora.org> | 2016-01-19 10:43:39 -0800 |
---|---|---|
committer | Rohit Vaswani <rvaswani@codeaurora.org> | 2016-03-01 12:22:38 -0800 |
commit | de64ab2e8604d5d5b99c36d6a593a4ed88a95aaf (patch) | |
tree | 6b87c8403dd093a81cfba3031f4f315dff2151dc /drivers/soc/qcom/spm_devices.c | |
parent | f9157b4ed20e81a86d6316bff5c666b76ae6f1a0 (diff) |
soc: spm: Snapshot of the SPM driver from 3.18 kernel
This is a snapshot of the SPM driver from 3.18 kernel. The upstream spm.c
file is used as a idle driver. So updated spm driver from 3.18 kernel to
msm-spm.c on 4.4 kernel.
Change-Id: I73b020214fdcc7eb695cf8f5b52cf7885a0a10cd
Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
Diffstat (limited to 'drivers/soc/qcom/spm_devices.c')
-rw-r--r-- | drivers/soc/qcom/spm_devices.c | 985 |
1 files changed, 985 insertions, 0 deletions
diff --git a/drivers/soc/qcom/spm_devices.c b/drivers/soc/qcom/spm_devices.c new file mode 100644 index 000000000000..31f015106bf9 --- /dev/null +++ b/drivers/soc/qcom/spm_devices.c @@ -0,0 +1,985 @@ +/* Copyright (c) 2011-2016, 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 <linux/cpu.h> +#include <soc/qcom/spm.h> +#include "spm_driver.h" + +#define VDD_DEFAULT 0xDEADF00D +#define SLP_CMD_BIT 17 +#define PC_MODE_BIT 16 +#define RET_MODE_BIT 15 +#define EVENT_SYNC_BIT 24 +#define ISAR_BIT 3 +#define SPM_EN_BIT 0 + +struct msm_spm_power_modes { + uint32_t mode; + uint32_t ctl; +}; + +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; + bool qchannel_ignore; + bool allow_rpm_hs; + bool use_spm_clk_gating; + bool use_qchannel_for_wfi; + void __iomem *flush_base_addr; + void __iomem *slpreq_base_addr; +}; + +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. + */ +int msm_spm_get_vdd(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -EPROBE_DEFER; + + return msm_spm_drv_get_vdd(&dev->reg_data) ? : -EINVAL; +} +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 = !dev->use_qchannel_for_wfi; + 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_STANDALONE_POWER_COLLAPSE: + case MSM_SPM_MODE_POWER_COLLAPSE: + qchannel_ignore = dev->qchannel_ignore; + spm_legacy_mode = 1; + break; + default: + break; + } + + val = spm_legacy_mode << 2 | qchannel_ignore << 1; + __raw_writel(val, dev->q2s_reg); + mb(); +} + +static void msm_spm_config_hw_flush(struct msm_spm_device *dev, + unsigned int mode) +{ + uint32_t val = 0; + + if (!dev->flush_base_addr) + return; + + switch (mode) { + case MSM_SPM_MODE_FASTPC: + case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE: + case MSM_SPM_MODE_POWER_COLLAPSE: + val = BIT(0); + break; + default: + break; + } + + __raw_writel(val, dev->flush_base_addr); +} + +static void msm_spm_config_slpreq(struct msm_spm_device *dev, + unsigned int mode) +{ + uint32_t val = 0; + + if (!dev->slpreq_base_addr) + return; + + switch (mode) { + case MSM_SPM_MODE_FASTPC: + case MSM_SPM_MODE_GDHS: + case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE: + case MSM_SPM_MODE_POWER_COLLAPSE: + val = BIT(4); + break; + default: + break; + } + + val = (__raw_readl(dev->slpreq_base_addr) & ~BIT(4)) | val; + __raw_writel(val, dev->slpreq_base_addr); +} + +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm) +{ + uint32_t i; + int ret = -EINVAL; + uint32_t ctl; + + if (!dev) { + pr_err("dev is NULL\n"); + return -ENODEV; + } + + if (!dev->initialized) + return -ENXIO; + + if (!dev->num_modes) + return 0; + + 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) + continue; + + ctl = dev->modes[i].ctl; + if (!dev->allow_rpm_hs && notify_rpm) + ctl &= ~BIT(SLP_CMD_BIT); + + break; + } + ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, ctl); + } + + msm_spm_config_q2s(dev, mode); + msm_spm_config_hw_flush(dev, mode); + msm_spm_config_slpreq(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; + + 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].ctl = data->modes[i].ctl | ((offset & 0x1FF) + << 4); + 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; + } + + msm_spm_drv_reinit(&dev->reg_data, dev->num_modes ? true : false); + + 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 + * @node: The SPM node that controls the voltage for the CPU + * @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(struct device_node *vctl_node, + unsigned int val, int cpu, int vctl_offset) +{ + uint32_t timeout = 2000; /* delay for voltage to settle on the core */ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + void __iomem *base; + + base = of_iomap(vctl_node, 1); + if (base) { + /* + * Program Q2S to disable SPM legacy mode and ignore Q2S + * channel requests. + * bit[1] = qchannel_ignore = 1 + * bit[2] = spm_legacy_mode = 0 + */ + writel_relaxed(0x2, base); + mb(); + iounmap(base); + } + + base = of_iomap(vctl_node, 0); + if (!base) + return -ENOMEM; + + if (dev && (dev->cpu_vdd != VDD_DEFAULT)) + return 0; + + /* Set the CPU supply regulator voltage */ + val = (val & 0xFF); + writel_relaxed(val, base + vctl_offset); + mb(); + udelay(timeout); + + /* Enable the CPU supply regulator*/ + val = 0x30080; + writel_relaxed(val, base + vctl_offset); + mb(); + udelay(timeout); + + iounmap(base); + + 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), true); +} +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_is_avs_enabled() - Functions returns 1 if AVS is enabled and + * 0 if it is not. + * @cpu: specifies cpu's avs should be read + * + * Returns errno in case of failure or AVS enable state otherwise + */ +int msm_spm_is_avs_enabled(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_get_avs_enable(&dev->reg_data); +} +EXPORT_SYMBOL(msm_spm_is_avs_enabled); + +/** + * msm_spm_avs_enable() - Enables AVS on the SAW that controls this cpu's + * voltage. + * @cpu: specifies which cpu's avs should be enabled + * + * Returns errno in case of failure or 0 if successful + */ +int msm_spm_avs_enable(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_enable(&dev->reg_data, true); +} +EXPORT_SYMBOL(msm_spm_avs_enable); + +/** + * msm_spm_avs_disable() - Disables AVS on the SAW that controls this cpu's + * voltage. + * @cpu: specifies which cpu's avs should be enabled + * + * Returns errno in case of failure or 0 if successful + */ +int msm_spm_avs_disable(unsigned int cpu) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_enable(&dev->reg_data, false); +} +EXPORT_SYMBOL(msm_spm_avs_disable); + +/** + * msm_spm_avs_set_limit() - Set maximum and minimum AVS limits on the + * SAW that controls this cpu's voltage. + * @cpu: specify which cpu's avs should be configured + * @min_lvl: specifies the minimum PMIC output voltage control register + * value that may be sent to the PMIC + * @max_lvl: specifies the maximum PMIC output voltage control register + * value that may be sent to the PMIC + * Returns errno in case of failure or 0 if successful + */ +int msm_spm_avs_set_limit(unsigned int cpu, + uint32_t min_lvl, uint32_t max_lvl) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_limit(&dev->reg_data, min_lvl, max_lvl); +} +EXPORT_SYMBOL(msm_spm_avs_set_limit); + +/** + * msm_spm_avs_enable_irq() - Enable an AVS interrupt + * @cpu: specifies which CPU's AVS should be configured + * @irq: specifies which interrupt to enable + * + * Returns errno in case of failure or 0 if successful. + */ +int msm_spm_avs_enable_irq(unsigned int cpu, enum msm_spm_avs_irq irq) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_irq_enable(&dev->reg_data, irq, true); +} +EXPORT_SYMBOL(msm_spm_avs_enable_irq); + +/** + * msm_spm_avs_disable_irq() - Disable an AVS interrupt + * @cpu: specifies which CPU's AVS should be configured + * @irq: specifies which interrupt to disable + * + * Returns errno in case of failure or 0 if successful. + */ +int msm_spm_avs_disable_irq(unsigned int cpu, enum msm_spm_avs_irq irq) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_set_avs_irq_enable(&dev->reg_data, irq, false); +} +EXPORT_SYMBOL(msm_spm_avs_disable_irq); + +/** + * msm_spm_avs_clear_irq() - Clear a latched AVS interrupt + * @cpu: specifies which CPU's AVS should be configured + * @irq: specifies which interrupt to clear + * + * Returns errno in case of failure or 0 if successful. + */ +int msm_spm_avs_clear_irq(unsigned int cpu, enum msm_spm_avs_irq irq) +{ + struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu); + + if (!dev) + return -ENXIO; + + return msm_spm_drv_avs_clear_irq(&dev->reg_data, irq); +} +EXPORT_SYMBOL(msm_spm_avs_clear_irq); + +/** + * 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; + 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; + } + } else + return num_possible_cpus(); + + return -EINVAL; +} + +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 == num_possible_cpus()) + 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 c; + int idx = 0; + struct device_node *cpu_node; + char *key = "qcom,cpu-vctl-list"; + + cpu_node = of_parse_phandle(node, key, idx++); + while (cpu_node) { + 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++); + }; +} + +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 device_node *n = NULL; + 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_SAW_CFG}, + {"qcom,saw2-avs-ctl", MSM_SPM_REG_SAW_AVS_CTL}, + {"qcom,saw2-avs-hysteresis", MSM_SPM_REG_SAW_AVS_HYSTERESIS}, + {"qcom,saw2-avs-limit", MSM_SPM_REG_SAW_AVS_LIMIT}, + {"qcom,saw2-avs-dly", MSM_SPM_REG_SAW_AVS_DLY}, + {"qcom,saw2-spm-dly", MSM_SPM_REG_SAW_SPM_DLY}, + {"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW_SPM_CTL}, + {"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW_PMIC_DATA_0}, + {"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW_PMIC_DATA_1}, + {"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW_PMIC_DATA_2}, + {"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW_PMIC_DATA_3}, + {"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW_PMIC_DATA_4}, + {"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW_PMIC_DATA_5}, + {"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW_PMIC_DATA_6}, + {"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW_PMIC_DATA_7}, + }; + + struct mode_of { + char *key; + uint32_t id; + }; + + struct mode_of mode_of_data[] = { + {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING}, + {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION}, + {"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS}, + {"qcom,saw2-spm-cmd-spc", + MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE}, + {"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE}, + {"qcom,saw2-spm-cmd-fpc", MSM_SPM_MODE_FASTPC}, + }; + + dev = msm_spm_get_device(pdev); + if (!dev) { + /* + * For partial goods support some CPUs might not be available + * in which case, shouldn't throw an error + */ + return 0; + } + 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)); + + key = "qcom,saw2-ver-reg"; + ret = of_property_read_u32(node, key, &val); + if (ret) + goto fail; + spm_data.ver_reg = val; + + 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_byname(pdev, IORESOURCE_MEM, "q2s"); + 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; + } + } + + key = "qcom,use-qchannel-for-pc"; + dev->qchannel_ignore = !of_property_read_bool(node, key); + + key = "qcom,use-spm-clock-gating"; + dev->use_spm_clk_gating = of_property_read_bool(node, key); + + key = "qcom,use-qchannel-for-wfi"; + dev->use_qchannel_for_wfi = of_property_read_bool(node, key); + + /* HW flush address */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hw-flush"); + if (res) { + dev->flush_base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->flush_base_addr)) { + ret = PTR_ERR(dev->flush_base_addr); + pr_err("%s(): Unable to iomap hw flush register %d\n", + __func__, ret); + goto fail; + } + } + + /* Sleep req address */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "slpreq"); + if (res) { + dev->slpreq_base_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!dev->slpreq_base_addr) { + ret = -ENOMEM; + pr_err("%s(): Unable to iomap slpreq 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); + msm_spm_drv_reg_init(&dev->reg_data, &spm_data); + + 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; + msm_spm_drv_upd_reg_shadow(&dev->reg_data, spm_of_data[i].id, + val); + } + + for_each_child_of_node(node, n) { + const char *name; + bool bit_set; + int sync; + + if (!n->name) + continue; + + ret = of_property_read_string(n, "qcom,label", &name); + if (ret) + continue; + + for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) + if (!strcmp(name, mode_of_data[i].key)) + break; + + if (i == ARRAY_SIZE(mode_of_data)) { + pr_err("Mode name invalid %s\n", name); + break; + } + + modes[mode_count].mode = mode_of_data[i].id; + modes[mode_count].cmd = + (uint8_t *)of_get_property(n, "qcom,sequence", &len); + if (!modes[mode_count].cmd) { + pr_err("cmd is empty\n"); + continue; + } + + bit_set = of_property_read_bool(n, "qcom,pc_mode"); + modes[mode_count].ctl |= bit_set ? BIT(PC_MODE_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,ret_mode"); + modes[mode_count].ctl |= bit_set ? BIT(RET_MODE_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,slp_cmd_mode"); + modes[mode_count].ctl |= bit_set ? BIT(SLP_CMD_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,isar"); + modes[mode_count].ctl |= bit_set ? BIT(ISAR_BIT) : 0; + + bit_set = of_property_read_bool(n, "qcom,spm_en"); + modes[mode_count].ctl |= bit_set ? BIT(SPM_EN_BIT) : 0; + + ret = of_property_read_u32(n, "qcom,event_sync", &sync); + if (!ret) + modes[mode_count].ctl |= sync << EVENT_SYNC_BIT; + + mode_count++; + } + + spm_data.modes = modes; + spm_data.num_modes = mode_count; + + key = "qcom,supports-rpm-hs"; + dev->allow_rpm_hs = of_property_read_bool(pdev->dev.of_node, key); + + ret = msm_spm_dev_init(dev, &spm_data); + if (ret) + pr_err("SPM modes programming is not available from HLOS\n"); + + platform_set_drvdata(pdev, dev); + + for_each_cpu(cpu, &dev->mask) + per_cpu(cpu_vctl_device, cpu) = dev; + + if (!spm_data.num_modes) + return 0; + + cpu = get_cpu_id(pdev->dev.of_node); + + /* For CPUs that are online, the SPM has to be programmed for + * clockgating mode to ensure that it can use SPM for entering these + * low power modes. + */ + get_online_cpus(); + if ((cpu >= 0) && (cpu < num_possible_cpus()) && (cpu_online(cpu))) + msm_spm_config_low_power_mode(dev, MSM_SPM_MODE_CLOCK_GATING, + false); + put_online_cpus(); + 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"}, + {}, +}; + +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); +} +arch_initcall(msm_spm_device_init); |