[PATCH v6 1/5] qcom: spm: Add Subsystem Power Manager driver
Kumar Gala
galak at codeaurora.org
Wed Sep 24 11:07:51 PDT 2014
On Sep 23, 2014, at 6:51 PM, Lina Iyer <lina.iyer at linaro.org> wrote:
> Based on work by many authors, available at codeaurora.org
>
> SPM is a hardware block that controls the peripheral logic surrounding
> the application cores (cpu/l$). When the core executes WFI instruction,
> the SPM takes over the putting the core in low power state as
> configured. The wake up for the SPM is an interrupt at the GIC, which
> then completes the rest of low power mode sequence and brings the core
> out of low power mode.
>
> The SPM has a set of control registers that configure the SPMs
> individually based on the type of the core and the runtime conditions.
> SPM is a finite state machine block to which a sequence is provided and
> it interprets the bytes and executes them in sequence. Each low power
> mode that the core can enter into is provided to the SPM as a sequence.
>
> Configure the SPM to set the core (cpu or L2) into its low power mode,
> the index of the first command in the sequence is set in the SPM_CTL
> register. When the core executes ARM wfi instruction, it triggers the
> SPM state machine to start executing from that index. The SPM state
> machine waits until the interrupt occurs and starts executing the rest
> of the sequence until it hits the end of the sequence. The end of the
> sequence jumps the core out of its low power mode.
>
> Signed-off-by: Lina Iyer <lina.iyer at linaro.org>
> [lina: simplify the driver for initial submission, clean up and update
> commit text]
> ---
> Documentation/devicetree/bindings/arm/msm/spm.txt | 43 +++
> drivers/soc/qcom/Kconfig | 8 +
> drivers/soc/qcom/Makefile | 1 +
> drivers/soc/qcom/spm.c | 388 ++++++++++++++++++++++
> include/soc/qcom/spm.h | 38 +++
> 5 files changed, 478 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/arm/msm/spm.txt
> create mode 100644 drivers/soc/qcom/spm.c
> create mode 100644 include/soc/qcom/spm.h
General comment, lets use qcom instead of msm for various things.
[snip]
> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> index 7dcd554..cd249c4 100644
> --- a/drivers/soc/qcom/Kconfig
> +++ b/drivers/soc/qcom/Kconfig
> @@ -11,3 +11,11 @@ config QCOM_GSBI
>
> config QCOM_SCM
> bool
> +
> +config QCOM_PM
> + bool "Qualcomm Power Management"
> + depends on PM && ARCH_QCOM
> + help
> + QCOM Platform specific power driver to manage cores and L2 low power
> + modes. It interface with various system drivers to put the cores in
> + low power modes.
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index 70d52ed..20b329f 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -1,3 +1,4 @@
> obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
> +obj-$(CONFIG_QCOM_PM) += 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.c b/drivers/soc/qcom/spm.c
> new file mode 100644
> index 0000000..1fa6a96
> --- /dev/null
> +++ b/drivers/soc/qcom/spm.c
> @@ -0,0 +1,388 @@
> +/* 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 <soc/qcom/spm.h>
> +
> +#define NUM_SEQ_ENTRY 32
> +#define SPM_CTL_ENABLE BIT(0)
> +
> +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,
> +};
> +
> +static u32 reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
Why an array, can’t this just be an enum?
> + [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,
> +};
> +
> +struct spm_of {
> + char *key;
> + u32 id;
> +};
> +
> +struct msm_spm_mode {
> + u32 mode;
> + u32 start_addr;
> +};
> +
> +struct msm_spm_driver_data {
> + void __iomem *reg_base_addr;
> + u32 *reg_offsets;
> + struct msm_spm_mode *modes;
> + u32 num_modes;
> +};
> +
> +struct msm_spm_device {
> + bool initialized;
> + struct msm_spm_driver_data drv;
> +};
> +
> +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
> +
> +static const struct of_device_id msm_spm_match_table[] __initconst;
> +
> +static int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *drv,
> + u32 mode)
> +{
Can we just fold this into msm_spm_set_low_power_mode
>
> + int i;
> + u32 start_addr = 0;
> + u32 ctl_val;
> +
> + for (i = 0; i < drv->num_modes; i++) {
> + if (drv->modes[i].mode == mode) {
> + start_addr = drv->modes[i].start_addr;
> + break;
> + }
> + }
> +
> + if (i == drv->num_modes)
> + return -EINVAL;
> +
> + /* Update bits 10:4 in the SPM CTL register */
> + ctl_val = readl_relaxed(drv->reg_base_addr +
> + drv->reg_offsets[MSM_SPM_REG_SAW2_SPM_CTL]);
> + start_addr &= 0x7F;
> + start_addr <<= 4;
> + ctl_val &= 0xFFFFF80F;
> + ctl_val |= start_addr;
> + writel_relaxed(ctl_val, drv->reg_base_addr +
> + drv->reg_offsets[MSM_SPM_REG_SAW2_SPM_CTL]);
> + /* Ensure we have written the start address */
> + wmb();
> +
> + return 0;
> +}
> +
> +static int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *drv,
> + bool enable)
> +{
> + u32 value = enable ? 0x01 : 0x00;
> + u32 ctl_val;
> +
> + ctl_val = readl_relaxed(drv->reg_base_addr +
> + drv->reg_offsets[MSM_SPM_REG_SAW2_SPM_CTL]);
> +
> + /* Update SPM_CTL to enable/disable the SPM */
> + if ((ctl_val & SPM_CTL_ENABLE) != value) {
> + /* Clear the existing value and update */
> + ctl_val &= ~0x1;
> + ctl_val |= value;
> + writel_relaxed(ctl_val, drv->reg_base_addr +
> + drv->reg_offsets[MSM_SPM_REG_SAW2_SPM_CTL]);
> +
> + /* Ensure we have enabled/disabled before returning */
> + wmb();
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
> + * @mode: SPM LPM mode to enter
> + */
> +int msm_spm_set_low_power_mode(u32 mode)
> +{
> + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
> + int ret = -EINVAL;
> +
> + if (!dev->initialized)
> + return -ENXIO;
> +
> + if (mode == MSM_SPM_MODE_DISABLED)
> + ret = msm_spm_drv_set_spm_enable(&dev->drv, false);
> + else if (!msm_spm_drv_set_spm_enable(&dev->drv, true))
> + ret = msm_spm_drv_set_low_power_mode(&dev->drv, mode);
> +
This could become:
if (mode == MSM_SPM_MODE_DISABLED)
return msm_spm_drv_set_spm_enable(&dev->drv, false);
if (msm_spm_drv_set_spm_enable(&dev->drv, true))
return ret;
code from msm_spm_drv_set_low_power_mode
> + return ret;
> +}
> +EXPORT_SYMBOL(msm_spm_set_low_power_mode);
> +
> +static void append_seq_data(u32 *reg_seq_entry, u8 *cmd, u32 *offset)
> +{
> + u32 cmd_w;
> + u32 offset_w = *offset / 4;
> + u8 last_cmd;
> +
> + while (1) {
> + int i;
> +
> + cmd_w = 0;
> + last_cmd = 0;
> + cmd_w = reg_seq_entry[offset_w];
> +
> + for (i = (*offset % 4); i < 4; i++) {
> + last_cmd = *(cmd++);
> + cmd_w |= last_cmd << (i * 8);
> + (*offset)++;
> + if (last_cmd == 0x0f)
> + break;
> + }
> +
> + reg_seq_entry[offset_w++] = cmd_w;
> + if (last_cmd == 0x0f)
> + break;
> + }
> +}
> +
> +static int msm_spm_seq_init(struct msm_spm_device *spm_dev,
> + struct platform_device *pdev)
> +{
> + int i;
> + u8 *cmd;
> + void *addr;
> + u32 val;
> + u32 count = 0;
> + int offset = 0;
> + struct msm_spm_mode modes[MSM_SPM_MODE_NR];
> + u32 sequences[NUM_SEQ_ENTRY/4] = {0};
> + struct msm_spm_driver_data *drv = &spm_dev->drv;
> +
> + /* SPM sleep sequences */
> + struct spm_of mode_of_data[] = {
> + {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING},
> + {"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE},
> + };
> +
> + /**
> + * Compose the u32 array based on the individual bytes of the SPM
> + * sequence for each low power mode that we read from the DT.
> + * The sequences are appended if there is space available in the
> + * u32 after the end of the previous sequence.
> + */
> +
> + for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
> + cmd = (u8 *)of_get_property(pdev->dev.of_node,
> + mode_of_data[i].key, &val);
> + if (!cmd)
> + continue;
> + /* The last in the sequence should be 0x0F */
> + if (cmd[val - 1] != 0x0F)
> + continue;
> + modes[count].mode = mode_of_data[i].id;
> + modes[count].start_addr = offset;
> + append_seq_data(&sequences[0], cmd, &offset);
> + count++;
> + }
> +
> + /* Write the idle state sequences to SPM */
> + drv->modes = devm_kcalloc(&pdev->dev, count,
> + sizeof(modes[0]), GFP_KERNEL);
> + if (!drv->modes)
> + return -ENOMEM;
> +
> + drv->num_modes = count;
> + memcpy(drv->modes, modes, sizeof(modes[0]) * count);
> +
> + /* Flush the integer array */
> + addr = drv->reg_base_addr +
> + drv->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY];
> + for (i = 0; i < ARRAY_SIZE(sequences); i++, addr += 4)
> + writel_relaxed(sequences[i], addr);
> +
> + /* Ensure we flush the writes */
> + wmb();
> +
> + return 0;
> +}
> +
> +static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
> +{
> + struct msm_spm_device *dev = NULL;
> + struct device_node *cpu_node;
> + u32 cpu;
> +
> + cpu_node = of_parse_phandle(pdev->dev.of_node, "qcom,cpu", 0);
> + if (cpu_node) {
> + for_each_possible_cpu(cpu) {
> + if (of_get_cpu_node(cpu, NULL) == cpu_node)
> + dev = &per_cpu(msm_cpu_spm_device, cpu);
> + }
> + }
> +
> + return dev;
> +}
> +
> +static int msm_spm_dev_probe(struct platform_device *pdev)
> +{
> + int ret;
> + int i;
> + u32 val;
> + struct msm_spm_device *spm_dev;
> + struct resource *res;
> + const struct of_device_id *match_id;
> +
> + /* SPM Configuration registers */
> + struct spm_of spm_of_data[] = {
> + {"qcom,saw2-clk-div", MSM_SPM_REG_SAW2_CFG},
> + {"qcom,saw2-enable", MSM_SPM_REG_SAW2_SPM_CTL},
> + {"qcom,saw2-delays", MSM_SPM_REG_SAW2_SPM_DLY},
> + };
Remove this array and do explicit of parsing and register setting, so only explicitly set what we should.
> +
> + /* Get the right SPM device */
> + spm_dev = msm_spm_get_device(pdev);
> + if (IS_ERR_OR_NULL(spm_dev))
> + return -EINVAL;
> +
> + /* Get the SPM start address */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res) {
> + ret = -EINVAL;
> + goto fail;
> + }
> + spm_dev->drv.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
> + resource_size(res));
> + if (!spm_dev->drv.reg_base_addr) {
> + ret = -ENOMEM;
> + goto fail;
> + }
Can we move to using devm_ioremap_resource() to reduce the platform_get_resource/devm_ioremap combo
> +
> + match_id = of_match_node(msm_spm_match_table, pdev->dev.of_node);
> + if (!match_id)
> + return -ENODEV;
> +
> + /* Use the register offsets for the SPM version in use */
> + spm_dev->drv.reg_offsets = (u32 *)match_id->data;
> + if (!spm_dev->drv.reg_offsets)
> + return -EFAULT;
> +
> + /* Read the SPM idle state sequences */
> + ret = msm_spm_seq_init(spm_dev, pdev);
> + if (ret)
> + return ret;
> +
> + /* Read the SPM register values */
> + for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
> + ret = of_property_read_u32(pdev->dev.of_node,
> + spm_of_data[i].key, &val);
> + if (ret)
> + continue;
> + writel_relaxed(val, spm_dev->drv.reg_base_addr +
> + spm_dev->drv.reg_offsets[spm_of_data[i].id]);
> + }
Change this to explicit parsing of each of prop and add proper read/modify/writes so we only change the things in the registers we should be touching
> +
> + /* Flush all writes */
> + wmb();
> +
> + spm_dev->initialized = true;
> + return ret;
> +fail:
> + dev_err(&pdev->dev, "SPM device probe failed: %d\n", ret);
> + return ret;
> +}
> +
> +static const struct of_device_id msm_spm_match_table[] __initconst = {
> + {.compatible = "qcom,spm-v2.1", .data = reg_offsets_saw2_v2_1},
> + { },
> +};
> +
> +
> +static struct platform_driver msm_spm_device_driver = {
> + .probe = msm_spm_dev_probe,
> + .driver = {
> + .name = "spm",
> + .owner = THIS_MODULE,
> + .of_match_table = msm_spm_match_table,
> + },
> +};
> +
> +static int __init msm_spm_device_init(void)
> +{
> + return platform_driver_register(&msm_spm_device_driver);
> +}
> +device_initcall(msm_spm_device_init);
> diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
> new file mode 100644
> index 0000000..29686ef
> --- /dev/null
> +++ b/include/soc/qcom/spm.h
> @@ -0,0 +1,38 @@
> +/* 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
> +};
Why don’t we make this a named enum, than msm_spm_set_low_power_mode can take that enum
>
> +
> +struct msm_spm_device;
> +
> +#if defined(CONFIG_QCOM_PM)
> +
> +int msm_spm_set_low_power_mode(u32 mode);
So this could become
int qcom_spm_set_low_power_mode(enum qcom_spm_mode mode)
> +
> +#else
> +
> +static inline int msm_spm_set_low_power_mode(u32 mode)
> +{ return -ENOSYS; }
> +
> +#endif /* CONFIG_QCOM_PM */
> +
> +#endif /* __QCOM_SPM_H */
> --
> 1.9.1
>
--
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
More information about the linux-arm-kernel
mailing list