[PATCH v4 3/8] qcom: spm: Add Subsystem Power Manager driver (SAW2)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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: Praveen Chidambaram <pchidamb@xxxxxxxxxxxxxx>
Signed-off-by: Lina Iyer <lina.iyer@xxxxxxxxxx>
---
 drivers/soc/qcom/Makefile  |   1 +
 drivers/soc/qcom/spm-drv.h |  71 +++++++++++++++++
 drivers/soc/qcom/spm.c     | 195 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 267 insertions(+)
 create mode 100644 drivers/soc/qcom/spm-drv.h
 create mode 100644 drivers/soc/qcom/spm.c

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-drv.h b/drivers/soc/qcom/spm-drv.h
new file mode 100644
index 0000000..4f41f1b
--- /dev/null
+++ b/drivers/soc/qcom/spm-drv.h
@@ -0,0 +1,71 @@
+/* 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
+
+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_mode {
+	uint32_t mode;
+	uint8_t *cmd;
+	uint32_t start_addr;
+};
+
+struct msm_spm_driver_data {
+	void __iomem *reg_base_addr;
+	uint32_t reg_shadow[MSM_SPM_REG_NR];
+	uint32_t *reg_offsets;
+	struct msm_spm_mode *modes;
+	uint32_t num_modes;
+};
+
+int msm_spm_drv_init(struct msm_spm_driver_data *dev);
+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
+		uint32_t addr);
+int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
+		bool enable);
+
+#endif /* __QCOM_SPM_DRIVER_H */
diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
new file mode 100644
index 0000000..19b79d0
--- /dev/null
+++ b/drivers/soc/qcom/spm.c
@@ -0,0 +1,195 @@
+/* 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-drv.h"
+
+#define NUM_SEQ_ENTRY 32
+
+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 void flush_shadow(struct msm_spm_driver_data *drv,
+		unsigned int reg_index)
+{
+	__raw_writel(drv->reg_shadow[reg_index],
+			drv->reg_base_addr + drv->reg_offsets[reg_index]);
+}
+
+static void load_shadow(struct msm_spm_driver_data *drv,
+		unsigned int reg_index)
+{
+	drv->reg_shadow[reg_index] = __raw_readl(drv->reg_base_addr +
+						drv->reg_offsets[reg_index]);
+}
+
+static inline void set_start_addr(struct msm_spm_driver_data *drv,
+		uint32_t addr)
+{
+	/* Update bits 10:4 in the SPM CTL register */
+	addr &= 0x7F;
+	addr <<= 4;
+	drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
+	drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
+}
+
+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *drv,
+		uint32_t mode)
+{
+	int i;
+	uint32_t start_addr = 0;
+
+	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;
+
+	set_start_addr(drv, start_addr);
+	flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL);
+	/* Barrier to ensure we have written the start address */
+	wmb();
+
+	/* Update our shadow with the status changes, if any */
+	load_shadow(drv, MSM_SPM_REG_SAW2_SPM_STS);
+
+	return 0;
+}
+
+int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *drv, bool enable)
+{
+	uint32_t value = enable ? 0x01 : 0x00;
+
+	/* Update BIT(0) of SPM_CTL to enable/disable the SPM */
+	if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
+		/* Clear the existing value  and update */
+		drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
+		drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
+		flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL);
+		/* Ensure we have enabled/disabled before returning */
+		wmb();
+	}
+
+	return 0;
+}
+
+static void flush_seq_data(struct msm_spm_driver_data *drv,
+				uint32_t *reg_seq_entry)
+{
+	int i;
+
+	/* Write the 32 byte array into the SPM registers */
+	for (i = 0; i < NUM_SEQ_ENTRY; i++) {
+		__raw_writel(reg_seq_entry[i],
+			drv->reg_base_addr
+			+ drv->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
+			+ 4 * i);
+	}
+	/* Ensure that the changes are written */
+	wmb();
+}
+
+static void write_seq_data(struct msm_spm_driver_data *drv,
+		uint32_t *reg_seq_entry, uint8_t *cmd, uint32_t *offset)
+{
+	uint32_t cmd_w;
+	uint32_t offset_w = *offset / 4;
+	uint8_t 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;
+	}
+
+}
+
+int msm_spm_drv_init(struct msm_spm_driver_data *drv)
+{
+	int i;
+	int offset = 0;
+	uint32_t sequences[NUM_SEQ_ENTRY/sizeof(uint8_t)] = {0};
+
+	drv->reg_offsets = msm_spm_reg_offsets_saw2_v2_1;
+
+	/**
+	 * Compose the uint32 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
+	 * integer after the end of the previous sequence.
+	 */
+	for (i = 0; i < drv->num_modes; i++) {
+		drv->modes[i].start_addr = offset;
+		write_seq_data(drv, &sequences[0], drv->modes[i].cmd, &offset);
+	}
+
+	/* Flush the integer array */
+	flush_seq_data(drv, &sequences[0]);
+
+	/**
+	 * Initialize the hardware with the control registers that
+	 * we have read.
+	 */
+	for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0; i++)
+		flush_shadow(drv, i);
+
+	return 0;
+}
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux