diff --git a/Makefile b/Makefile
index 16279b603..797b2cfeb 100644
--- a/Makefile
+++ b/Makefile
@@ -1096,7 +1096,6 @@ $(eval $(call assert_booleans,\
         ENABLE_RUNTIME_INSTRUMENTATION \
         ENABLE_SME_FOR_NS \
         ENABLE_SME_FOR_SWD \
-        ENABLE_SPE_FOR_NS \
         ENABLE_SVE_FOR_NS \
         ENABLE_SVE_FOR_SWD \
         ERROR_DEPRECATED \
@@ -1182,6 +1181,7 @@ $(eval $(call assert_numerics,\
         ENABLE_FEAT_VHE \
         ENABLE_MPAM_FOR_LOWER_ELS \
         ENABLE_RME \
+        ENABLE_SPE_FOR_NS \
         ENABLE_TRF_FOR_NS \
         FW_ENC_STATUS \
         NR_OF_FW_BANKS \
diff --git a/bl31/bl31.mk b/bl31/bl31.mk
index b639920e5..e000f4f7b 100644
--- a/bl31/bl31.mk
+++ b/bl31/bl31.mk
@@ -87,7 +87,7 @@ BL31_SOURCES		+=	services/std_svc/trng/trng_main.c	\
 				services/std_svc/trng/trng_entropy_pool.c
 endif
 
-ifeq (${ENABLE_SPE_FOR_NS},1)
+ifneq (${ENABLE_SPE_FOR_NS},0)
 BL31_SOURCES		+=	lib/extensions/spe/spe.c
 endif
 
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index 9241c3912..9415871df 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -428,10 +428,11 @@ Common build options
    handle context switching for SME, SVE, and FPU/SIMD registers to ensure that
    no data is leaked to non-secure world. This is experimental. Default is 0.
 
--  ``ENABLE_SPE_FOR_NS`` : Boolean option to enable Statistical Profiling
+-  ``ENABLE_SPE_FOR_NS`` : Numeric value to enable Statistical Profiling
    extensions. This is an optional architectural feature for AArch64.
-   The default is 1 but is automatically disabled when the target architecture
-   is AArch32.
+   This flag can take the values 0 to 2, to align with the ``FEATURE_DETECTION``
+   mechanism. The default is 2 but is automatically disabled when the target
+   architecture is AArch32.
 
 -  ``ENABLE_SVE_FOR_NS``: Boolean option to enable Scalable Vector Extension
    (SVE) for the Non-secure world only. SVE is an optional architectural feature
diff --git a/include/arch/aarch32/arch_features.h b/include/arch/aarch32/arch_features.h
index a5a5e278b..12df6da4b 100644
--- a/include/arch/aarch32/arch_features.h
+++ b/include/arch/aarch32/arch_features.h
@@ -43,4 +43,10 @@ static inline bool is_feat_trf_supported(void)
 	return read_feat_trf_id_field() != 0U;
 }
 
+static inline bool is_feat_spe_supported(void)
+{
+	/* FEAT_SPE is AArch64 only */
+	return false;
+}
+
 #endif /* ARCH_FEATURES_H */
diff --git a/include/arch/aarch64/arch_features.h b/include/arch/aarch64/arch_features.h
index 582aed12f..58b37521b 100644
--- a/include/arch/aarch64/arch_features.h
+++ b/include/arch/aarch64/arch_features.h
@@ -249,10 +249,22 @@ static inline bool is_armv8_0_feat_csv2_2_present(void)
 /**********************************************************************************
  * Function to identify the presence of FEAT_SPE (Statistical Profiling Extension)
  *********************************************************************************/
-static inline bool is_armv8_2_feat_spe_present(void)
+static inline unsigned int read_feat_spe_id_field(void)
 {
-	return (((read_id_aa64dfr0_el1() >> ID_AA64DFR0_PMS_SHIFT) &
-		ID_AA64DFR0_PMS_MASK) != ID_AA64DFR0_SPE_NOT_SUPPORTED);
+	return ISOLATE_FIELD(read_id_aa64dfr0_el1(), ID_AA64DFR0_PMS);
+}
+
+static inline bool is_feat_spe_supported(void)
+{
+	if (ENABLE_SPE_FOR_NS == FEAT_STATE_DISABLED) {
+		return false;
+	}
+
+	if (ENABLE_SPE_FOR_NS == FEAT_STATE_ALWAYS) {
+		return true;
+	}
+
+	return read_feat_spe_id_field() != 0U;
 }
 
 /*******************************************************************************
diff --git a/include/lib/extensions/spe.h b/include/lib/extensions/spe.h
index d4b925fe4..d443f18fb 100644
--- a/include/lib/extensions/spe.h
+++ b/include/lib/extensions/spe.h
@@ -9,8 +9,16 @@
 
 #include <stdbool.h>
 
-bool spe_supported(void);
+#if ENABLE_SPE_FOR_NS
 void spe_enable(bool el2_unused);
 void spe_disable(void);
+#else
+void spe_enable(bool el2_unused)
+{
+}
+void spe_disable(void)
+{
+}
+#endif
 
 #endif /* SPE_H */
diff --git a/lib/el3_runtime/aarch64/context_mgmt.c b/lib/el3_runtime/aarch64/context_mgmt.c
index 50fddc502..c5f20ad35 100644
--- a/lib/el3_runtime/aarch64/context_mgmt.c
+++ b/lib/el3_runtime/aarch64/context_mgmt.c
@@ -482,9 +482,9 @@ void cm_setup_context(cpu_context_t *ctx, const entry_point_info_t *ep)
 static void manage_extensions_nonsecure(bool el2_unused, cpu_context_t *ctx)
 {
 #if IMAGE_BL31
-#if ENABLE_SPE_FOR_NS
-	spe_enable(el2_unused);
-#endif
+	if (is_feat_spe_supported()) {
+		spe_enable(el2_unused);
+	}
 
 #if ENABLE_AMU
 	amu_enable(el2_unused, ctx);
diff --git a/lib/extensions/spe/spe.c b/lib/extensions/spe/spe.c
index d747efcd7..b1fe39f58 100644
--- a/lib/extensions/spe/spe.c
+++ b/lib/extensions/spe/spe.c
@@ -7,6 +7,7 @@
 #include <stdbool.h>
 
 #include <arch.h>
+#include <arch_features.h>
 #include <arch_helpers.h>
 #include <lib/el3_runtime/pubsub.h>
 #include <lib/extensions/spe.h>
@@ -20,21 +21,10 @@ static inline void psb_csync(void)
 	__asm__ volatile("hint #17");
 }
 
-bool spe_supported(void)
-{
-	uint64_t features;
-
-	features = read_id_aa64dfr0_el1() >> ID_AA64DFR0_PMS_SHIFT;
-	return (features & ID_AA64DFR0_PMS_MASK) > 0ULL;
-}
-
 void spe_enable(bool el2_unused)
 {
 	uint64_t v;
 
-	if (!spe_supported())
-		return;
-
 	if (el2_unused) {
 		/*
 		 * MDCR_EL2.TPMS (ARM v8.2): Do not trap statistical
@@ -69,9 +59,6 @@ void spe_disable(void)
 {
 	uint64_t v;
 
-	if (!spe_supported())
-		return;
-
 	/* Drain buffered data */
 	psb_csync();
 	dsbnsh();
@@ -85,7 +72,7 @@ void spe_disable(void)
 
 static void *spe_drain_buffers_hook(const void *arg)
 {
-	if (!spe_supported())
+	if (!is_feat_spe_supported())
 		return (void *)-1;
 
 	/* Drain buffered data */
diff --git a/make_helpers/defaults.mk b/make_helpers/defaults.mk
index 63ac82e6d..b928fcf98 100644
--- a/make_helpers/defaults.mk
+++ b/make_helpers/defaults.mk
@@ -355,7 +355,7 @@ V				:= 0
 WARMBOOT_ENABLE_DCACHE_EARLY	:= 0
 
 # Build option to enable/disable the Statistical Profiling Extensions
-ENABLE_SPE_FOR_NS		:= 1
+ENABLE_SPE_FOR_NS		:= 2
 
 # SPE is only supported on AArch64 so disable it on AArch32.
 ifeq (${ARCH},aarch32)
diff --git a/plat/arm/board/arm_fpga/fpga_bl31_setup.c b/plat/arm/board/arm_fpga/fpga_bl31_setup.c
index e1b3abb28..5cc1e7645 100644
--- a/plat/arm/board/arm_fpga/fpga_bl31_setup.c
+++ b/plat/arm/board/arm_fpga/fpga_bl31_setup.c
@@ -364,7 +364,7 @@ static void fpga_prepare_dtb(void)
 	fpga_dtb_update_clock(fdt, system_freq);
 
 	/* Check whether we support the SPE PMU. Remove the DT node if not. */
-	if (!spe_supported()) {
+	if (!is_feat_spe_supported()) {
 		int node = fdt_node_offset_by_compatible(fdt, 0,
 				     "arm,statistical-profiling-extension-v1");
 
diff --git a/plat/arm/board/fvp/fvp_pm.c b/plat/arm/board/fvp/fvp_pm.c
index 9d9386230..3d53388e4 100644
--- a/plat/arm/board/fvp/fvp_pm.c
+++ b/plat/arm/board/fvp/fvp_pm.c
@@ -6,6 +6,7 @@
 
 #include <assert.h>
 
+#include <arch_features.h>
 #include <arch_helpers.h>
 #include <common/debug.h>
 #include <drivers/arm/gicv3.h>
@@ -53,13 +54,13 @@ static void fvp_cluster_pwrdwn_common(void)
 {
 	uint64_t mpidr = read_mpidr_el1();
 
-#if ENABLE_SPE_FOR_NS
 	/*
 	 * On power down we need to disable statistical profiling extensions
 	 * before exiting coherency.
 	 */
-	spe_disable();
-#endif
+	if (is_feat_spe_supported()) {
+		spe_disable();
+	}
 
 	/* Disable coherency if this cluster is to be turned off */
 	fvp_interconnect_disable();