diff --git a/docs/firmware-design.md b/docs/firmware-design.md
index 68cad2e3bfb8ff8a72817543ad02b83e29b46336..90ce4b1bf5b0979b5bb601c74edd0c4f8aec7a6d 100644
--- a/docs/firmware-design.md
+++ b/docs/firmware-design.md
@@ -716,7 +716,7 @@ required support.
 |`PSCI_FEATURES`        | Yes     |                                           |
 |`CPU_FREEZE`           | No      |                                           |
 |`CPU_DEFAULT_SUSPEND`  | No      |                                           |
-|`CPU_HW_STATE`         | No      |                                           |
+|`NODE_HW_STATE`        | Yes*    |                                           |
 |`SYSTEM_SUSPEND`       | Yes*    |                                           |
 |`PSCI_SET_SUSPEND_MODE`| No      |                                           |
 |`PSCI_STAT_RESIDENCY`  | Yes*    |                                           |
diff --git a/docs/porting-guide.md b/docs/porting-guide.md
index 8dad4a0516ce902dabddf4f095966c165fc10ddf..195c9374decd4c2846788fc51cd83f4ee2e66e77 100644
--- a/docs/porting-guide.md
+++ b/docs/porting-guide.md
@@ -1832,6 +1832,20 @@ This function can also be used in case the platform wants to support local
 power state encoding for `power_state` parameter of PSCI_STAT_COUNT/RESIDENCY
 APIs as described in Section 5.18 of [PSCI].
 
+#### plat_psci_ops.get_node_hw_state()
+
+This is an optional function. If implemented this function is intended to return
+the power state of a node (identified by the first parameter, the `MPIDR`) in
+the power domain topology (identified by the second parameter, `power_level`),
+as retrieved from a power controller or equivalent component on the platform.
+Upon successful completion, the implementation must map and return the final
+status among `HW_ON`, `HW_OFF` or `HW_STANDBY`. Upon encountering failures, it
+must return either `PSCI_E_INVALID_PARAMS` or `PSCI_E_NOT_SUPPORTED` as
+appropriate.
+
+Implementations are not expected to handle `power_levels` greater than
+`PLAT_MAX_PWR_LVL`.
+
 3.6  Interrupt Management framework (in BL31)
 ----------------------------------------------
 BL31 implements an Interrupt Management Framework (IMF) to manage interrupts
diff --git a/include/lib/psci/psci.h b/include/lib/psci/psci.h
index a583fef7e680bc5869017a854950071d77a778c4..02cbbf3555ba0379c10fa5dc8de50b5673b9c908 100644
--- a/include/lib/psci/psci.h
+++ b/include/lib/psci/psci.h
@@ -78,6 +78,8 @@
 #define PSCI_SYSTEM_OFF			0x84000008
 #define PSCI_SYSTEM_RESET		0x84000009
 #define PSCI_FEATURES			0x8400000A
+#define PSCI_NODE_HW_STATE_AARCH32	0x8400000d
+#define PSCI_NODE_HW_STATE_AARCH64	0xc400000d
 #define PSCI_SYSTEM_SUSPEND_AARCH32	0x8400000E
 #define PSCI_SYSTEM_SUSPEND_AARCH64	0xc400000E
 #define PSCI_STAT_RESIDENCY_AARCH32	0x84000010
@@ -199,6 +201,17 @@ typedef enum {
 	AFF_STATE_ON_PENDING = 2
 } aff_info_state_t;
 
+/*
+ * These are the power states reported by PSCI_NODE_HW_STATE API for the
+ * specified CPU. The definitions of these states can be found in Section 5.15.3
+ * of PSCI specification (ARM DEN 0022C).
+ */
+typedef enum {
+	HW_ON = 0,
+	HW_OFF = 1,
+	HW_STANDBY = 2
+} node_hw_state_t;
+
 /*
  * Macro to represent invalid affinity level within PSCI.
  */
@@ -293,6 +306,7 @@ typedef struct plat_psci_ops {
 	int (*translate_power_state_by_mpidr)(u_register_t mpidr,
 				    unsigned int power_state,
 				    psci_power_state_t *output_state);
+	int (*get_node_hw_state)(u_register_t mpidr, unsigned int power_level);
 } plat_psci_ops_t;
 
 /*******************************************************************************
@@ -330,6 +344,8 @@ int psci_affinity_info(u_register_t target_affinity,
 int psci_migrate(u_register_t target_cpu);
 int psci_migrate_info_type(void);
 long psci_migrate_info_up_cpu(void);
+int psci_node_hw_state(u_register_t target_cpu,
+		       unsigned int power_level);
 int psci_features(unsigned int psci_fid);
 void __dead2 psci_power_down_wfi(void);
 void psci_arch_setup(void);
diff --git a/include/plat/arm/css/common/css_def.h b/include/plat/arm/css/common/css_def.h
index 636daf294a567aea5cf8fae2c376c74694c6555c..173de1b4d32f9cfbb8d7e96a6887e1b13ee5c6ce 100644
--- a/include/plat/arm/css/common/css_def.h
+++ b/include/plat/arm/css/common/css_def.h
@@ -148,5 +148,15 @@
 /* Trusted mailbox base address common to all CSS */
 #define PLAT_ARM_TRUSTED_MAILBOX_BASE	ARM_TRUSTED_SRAM_BASE
 
+/*
+ * Parsing of CPU and Cluster states, as returned by 'Get CSS Power State' SCP
+ * command
+ */
+#define CSS_CLUSTER_PWR_STATE_ON	0
+#define CSS_CLUSTER_PWR_STATE_OFF	3
+
+#define CSS_CPU_PWR_STATE_ON		1
+#define CSS_CPU_PWR_STATE_OFF		0
+#define CSS_CPU_PWR_STATE(state, n)	(((state) >> (n)) & 1)
 
 #endif /* __CSS_DEF_H__ */
diff --git a/include/plat/arm/css/common/css_pm.h b/include/plat/arm/css/common/css_pm.h
index ea6a5d251fd9a99822a78c13fd45f7f2f6b6ff87..4a6ca816162317c5328e895611962d280703acd5 100644
--- a/include/plat/arm/css/common/css_pm.h
+++ b/include/plat/arm/css/common/css_pm.h
@@ -45,5 +45,6 @@ void __dead2 css_system_off(void);
 void __dead2 css_system_reset(void);
 void css_cpu_standby(plat_local_state_t cpu_state);
 void css_get_sys_suspend_power_state(psci_power_state_t *req_state);
+int css_node_hw_state(u_register_t mpidr, unsigned int power_level);
 
 #endif /* __CSS_PM_H__ */
diff --git a/lib/psci/psci_main.c b/lib/psci/psci_main.c
index 3ad3dd40ef12132ff7493fc097a970e3e5d814f6..23bd106ca83fc501c90b886a2436f21aca6c6b6c 100644
--- a/lib/psci/psci_main.c
+++ b/lib/psci/psci_main.c
@@ -295,6 +295,31 @@ long psci_migrate_info_up_cpu(void)
 	return resident_cpu_mpidr;
 }
 
+int psci_node_hw_state(u_register_t target_cpu,
+		       unsigned int power_level)
+{
+	int rc;
+
+	/* Validate target_cpu */
+	rc = psci_validate_mpidr(target_cpu);
+	if (rc != PSCI_E_SUCCESS)
+		return PSCI_E_INVALID_PARAMS;
+
+	/* Validate power_level against PLAT_MAX_PWR_LVL */
+	if (power_level > PLAT_MAX_PWR_LVL)
+		return PSCI_E_INVALID_PARAMS;
+
+	/*
+	 * Dispatch this call to platform to query power controller, and pass on
+	 * to the caller what it returns
+	 */
+	assert(psci_plat_pm_ops->get_node_hw_state);
+	rc = psci_plat_pm_ops->get_node_hw_state(target_cpu, power_level);
+	assert((rc >= HW_ON && rc <= HW_STANDBY) || rc == PSCI_E_NOT_SUPPORTED
+			|| rc == PSCI_E_INVALID_PARAMS);
+	return rc;
+}
+
 int psci_features(unsigned int psci_fid)
 {
 	unsigned int local_caps = psci_caps;
@@ -378,6 +403,9 @@ u_register_t psci_smc_handler(uint32_t smc_fid,
 		case PSCI_MIG_INFO_UP_CPU_AARCH32:
 			return psci_migrate_info_up_cpu();
 
+		case PSCI_NODE_HW_STATE_AARCH32:
+			return psci_node_hw_state(x1, x2);
+
 		case PSCI_SYSTEM_SUSPEND_AARCH32:
 			return psci_system_suspend(x1, x2);
 
@@ -422,6 +450,9 @@ u_register_t psci_smc_handler(uint32_t smc_fid,
 		case PSCI_MIG_INFO_UP_CPU_AARCH64:
 			return psci_migrate_info_up_cpu();
 
+		case PSCI_NODE_HW_STATE_AARCH64:
+			return psci_node_hw_state(x1, x2);
+
 		case PSCI_SYSTEM_SUSPEND_AARCH64:
 			return psci_system_suspend(x1, x2);
 
diff --git a/lib/psci/psci_private.h b/lib/psci/psci_private.h
index b795c8e037ef6ede91a3c8d51c0dab407429cf83..781b3b5265c946dd0adee33a0a2140998acf5e67 100644
--- a/lib/psci/psci_private.h
+++ b/lib/psci/psci_private.h
@@ -68,6 +68,7 @@
 			define_psci_cap(PSCI_AFFINITY_INFO_AARCH64) |	\
 			define_psci_cap(PSCI_MIG_AARCH64) |		\
 			define_psci_cap(PSCI_MIG_INFO_UP_CPU_AARCH64) |	\
+			define_psci_cap(PSCI_NODE_HW_STATE_AARCH64) |	\
 			define_psci_cap(PSCI_SYSTEM_SUSPEND_AARCH64) |	\
 			define_psci_cap(PSCI_STAT_RESIDENCY_AARCH64) |	\
 			define_psci_cap(PSCI_STAT_COUNT_AARCH64))
diff --git a/lib/psci/psci_setup.c b/lib/psci/psci_setup.c
index aa2ed51027ac61ac60360c8d3af1867402bfd195..263ab68f944b49cd84319b6ee7b5a684afb0a621 100644
--- a/lib/psci/psci_setup.c
+++ b/lib/psci/psci_setup.c
@@ -263,6 +263,8 @@ int psci_setup(uintptr_t mailbox_ep)
 		psci_caps |=  define_psci_cap(PSCI_SYSTEM_OFF);
 	if (psci_plat_pm_ops->system_reset)
 		psci_caps |=  define_psci_cap(PSCI_SYSTEM_RESET);
+	if (psci_plat_pm_ops->get_node_hw_state)
+		psci_caps |= define_psci_cap(PSCI_NODE_HW_STATE_AARCH64);
 
 #if ENABLE_PSCI_STAT
 	psci_caps |=  define_psci_cap(PSCI_STAT_RESIDENCY_AARCH64);
diff --git a/plat/arm/board/fvp/fvp_pm.c b/plat/arm/board/fvp/fvp_pm.c
index 3976ef2b2b025a503e4ae2c489d0b995ea9c0506..66c0c3df963de4cfd18fdf23f15397d1f12faec7 100644
--- a/plat/arm/board/fvp/fvp_pm.c
+++ b/plat/arm/board/fvp/fvp_pm.c
@@ -287,6 +287,42 @@ static void __dead2 fvp_system_reset(void)
 	panic();
 }
 
+static int fvp_node_hw_state(u_register_t target_cpu,
+			     unsigned int power_level)
+{
+	unsigned int psysr;
+	int ret;
+
+	/*
+	 * The format of 'power_level' is implementation-defined, but 0 must
+	 * mean a CPU. We also allow 1 to denote the cluster
+	 */
+	if (power_level != ARM_PWR_LVL0 && power_level != ARM_PWR_LVL1)
+		return PSCI_E_INVALID_PARAMS;
+
+	/*
+	 * Read the status of the given MPDIR from FVP power controller. The
+	 * power controller only gives us on/off status, so map that to expected
+	 * return values of the PSCI call
+	 */
+	psysr = fvp_pwrc_read_psysr(target_cpu);
+	if (psysr == PSYSR_INVALID)
+		return PSCI_E_INVALID_PARAMS;
+
+	switch (power_level) {
+	case ARM_PWR_LVL0:
+		ret = (psysr & PSYSR_AFF_L0) ? HW_ON : HW_OFF;
+		break;
+	case ARM_PWR_LVL1:
+		ret = (psysr & PSYSR_AFF_L1) ? HW_ON : HW_OFF;
+		break;
+	default:
+		assert(0);
+	}
+
+	return ret;
+}
+
 /*******************************************************************************
  * Export the platform handlers via plat_arm_psci_pm_ops. The ARM Standard
  * platform layer will take care of registering the handlers with PSCI.
@@ -301,5 +337,6 @@ const plat_psci_ops_t plat_arm_psci_pm_ops = {
 	.system_off = fvp_system_off,
 	.system_reset = fvp_system_reset,
 	.validate_power_state = arm_validate_power_state,
-	.validate_ns_entrypoint = arm_validate_ns_entrypoint
+	.validate_ns_entrypoint = arm_validate_ns_entrypoint,
+	.get_node_hw_state = fvp_node_hw_state
 };
diff --git a/plat/arm/board/juno/juno_pm.c b/plat/arm/board/juno/juno_pm.c
index cbf994a4713f0ff10a6b33b4c892520c245ab905..c355d94dfbbb4da985d6d2251c92f7c374537967 100644
--- a/plat/arm/board/juno/juno_pm.c
+++ b/plat/arm/board/juno/juno_pm.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2015-2016, ARM Limited and Contributors. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -88,5 +88,6 @@ const plat_psci_ops_t plat_arm_psci_pm_ops = {
 	.validate_power_state		= juno_validate_power_state,
 	.validate_ns_entrypoint		= arm_validate_ns_entrypoint,
 	.get_sys_suspend_power_state	= css_get_sys_suspend_power_state,
-	.translate_power_state_by_mpidr = juno_translate_power_state_by_mpidr
+	.translate_power_state_by_mpidr = juno_translate_power_state_by_mpidr,
+	.get_node_hw_state		= css_node_hw_state
 };
diff --git a/plat/arm/css/common/css_pm.c b/plat/arm/css/common/css_pm.c
index 801d937578567e2823a42f583cb96da6e99eb1fb..7607f617762dcdb7be5fdb74a8d057b2551a39b5 100644
--- a/plat/arm/css/common/css_pm.c
+++ b/plat/arm/css/common/css_pm.c
@@ -298,6 +298,43 @@ void css_get_sys_suspend_power_state(psci_power_state_t *req_state)
 		req_state->pwr_domain_state[i] = ARM_LOCAL_STATE_OFF;
 }
 
+/*******************************************************************************
+ * Handler to query CPU/cluster power states from SCP
+ ******************************************************************************/
+int css_node_hw_state(u_register_t mpidr, unsigned int power_level)
+{
+	int rc, element;
+	unsigned int cpu_state, cluster_state;
+
+	/*
+	 * The format of 'power_level' is implementation-defined, but 0 must
+	 * mean a CPU. We also allow 1 to denote the cluster
+	 */
+	if (power_level != ARM_PWR_LVL0 && power_level != ARM_PWR_LVL1)
+		return PSCI_E_INVALID_PARAMS;
+
+	/* Query SCP */
+	rc = scpi_get_css_power_state(mpidr, &cpu_state, &cluster_state);
+	if (rc != 0)
+		return PSCI_E_INVALID_PARAMS;
+
+	/* Map power states of CPU and cluster to expected PSCI return codes */
+	if (power_level == ARM_PWR_LVL0) {
+		/*
+		 * The CPU state returned by SCP is an 8-bit bit mask
+		 * corresponding to each CPU in the cluster
+		 */
+		element = mpidr & MPIDR_AFFLVL_MASK;
+		return CSS_CPU_PWR_STATE(cpu_state, element) ==
+			CSS_CPU_PWR_STATE_ON ? HW_ON : HW_OFF;
+	} else {
+		assert(cluster_state == CSS_CLUSTER_PWR_STATE_ON ||
+				cluster_state == CSS_CLUSTER_PWR_STATE_OFF);
+		return cluster_state == CSS_CLUSTER_PWR_STATE_ON ? HW_ON :
+			HW_OFF;
+	}
+}
+
 /*******************************************************************************
  * Export the platform handlers via plat_arm_psci_pm_ops. The ARM Standard
  * platform will take care of registering the handlers with PSCI.
@@ -312,5 +349,6 @@ const plat_psci_ops_t plat_arm_psci_pm_ops = {
 	.system_off		= css_system_off,
 	.system_reset		= css_system_reset,
 	.validate_power_state	= arm_validate_power_state,
-	.validate_ns_entrypoint = arm_validate_ns_entrypoint
+	.validate_ns_entrypoint = arm_validate_ns_entrypoint,
+	.get_node_hw_state	= css_node_hw_state
 };
diff --git a/plat/arm/css/common/css_scpi.c b/plat/arm/css/common/css_scpi.c
index 02d573c9fb6d78ff8fc2d4202bb79c63fbbb40cd..90a8939d985266903deb0185b7ff54490a456448 100644
--- a/plat/arm/css/common/css_scpi.c
+++ b/plat/arm/css/common/css_scpi.c
@@ -41,11 +41,18 @@
 #define SCPI_SHARED_MEM_AP_TO_SCP	(PLAT_CSS_SCP_COM_SHARED_MEM_BASE \
 								 + 0x100)
 
+/* Header and payload addresses for commands from AP to SCP */
 #define SCPI_CMD_HEADER_AP_TO_SCP		\
 	((scpi_cmd_t *) SCPI_SHARED_MEM_AP_TO_SCP)
 #define SCPI_CMD_PAYLOAD_AP_TO_SCP		\
 	((void *) (SCPI_SHARED_MEM_AP_TO_SCP + sizeof(scpi_cmd_t)))
 
+/* Header and payload addresses for responses from SCP to AP */
+#define SCPI_RES_HEADER_SCP_TO_AP \
+	((scpi_cmd_t *) SCPI_SHARED_MEM_SCP_TO_AP)
+#define SCPI_RES_PAYLOAD_SCP_TO_AP \
+	((void *) (SCPI_SHARED_MEM_SCP_TO_AP + sizeof(scpi_cmd_t)))
+
 /* ID of the MHU slot used for the SCPI protocol */
 #define SCPI_MHU_SLOT_ID		0
 
@@ -163,6 +170,68 @@ void scpi_set_css_power_state(unsigned mpidr, scpi_power_state_t cpu_state,
 	scpi_secure_message_end();
 }
 
+/*
+ * Query and obtain CSS power state from SCP.
+ *
+ * In response to the query, SCP returns power states of all CPUs in all
+ * clusters of the system. The returned response is then filtered based on the
+ * supplied MPIDR. Power states of requested cluster and CPUs within are updated
+ * via. supplied non-NULL pointer arguments.
+ *
+ * Returns 0 on success, or -1 on errors.
+ */
+int scpi_get_css_power_state(unsigned int mpidr, unsigned int *cpu_state_p,
+		unsigned int *cluster_state_p)
+{
+	scpi_cmd_t *cmd;
+	scpi_cmd_t response;
+	int power_state, cpu, cluster, rc = -1;
+
+	/*
+	 * Extract CPU and cluster membership of the given MPIDR. SCPI caters
+	 * for only up to 0xf clusters, and 8 CPUs per cluster
+	 */
+	cpu = mpidr & MPIDR_AFFLVL_MASK;
+	cluster = (mpidr >> MPIDR_AFF1_SHIFT) & MPIDR_AFFLVL_MASK;
+	if (cpu >= 8 || cluster >= 0xf)
+		return -1;
+
+	scpi_secure_message_start();
+
+	/* Populate request headers */
+	cmd = memset(SCPI_CMD_HEADER_AP_TO_SCP, 0, sizeof(*cmd));
+	cmd->id = SCPI_CMD_GET_CSS_POWER_STATE;
+
+	/*
+	 * Send message and wait for SCP's response
+	 */
+	scpi_secure_message_send(0);
+	scpi_secure_message_receive(&response);
+
+	if (response.status != SCP_OK)
+		goto exit;
+
+	/* Validate SCP response */
+	if (!CHECK_RESPONSE(response, cluster))
+		goto exit;
+
+	/* Extract power states for required cluster */
+	power_state = *(((uint16_t *) SCPI_RES_PAYLOAD_SCP_TO_AP) + cluster);
+	if (CLUSTER_ID(power_state) != cluster)
+		goto exit;
+
+	/* Update power state via. pointers */
+	if (cluster_state_p)
+		*cluster_state_p = CLUSTER_POWER_STATE(power_state);
+	if (cpu_state_p)
+		*cpu_state_p = CPU_POWER_STATE(power_state);
+	rc = 0;
+
+exit:
+	scpi_secure_message_end();
+	return rc;
+}
+
 uint32_t scpi_sys_power_state(scpi_system_state_t system_state)
 {
 	scpi_cmd_t *cmd;
diff --git a/plat/arm/css/common/css_scpi.h b/plat/arm/css/common/css_scpi.h
index 4a601f3ef14f5c3d7ce7e24bddc492c1a8d37bf8..1fb55e4ddc8e4f107a2f8148b752ecf16d65cd01 100644
--- a/plat/arm/css/common/css_scpi.h
+++ b/plat/arm/css/common/css_scpi.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014-2015, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2014-2016, ARM Limited and Contributors. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -81,9 +81,34 @@ typedef uint32_t scpi_status_t;
 typedef enum {
 	SCPI_CMD_SCP_READY = 0x01,
 	SCPI_CMD_SET_CSS_POWER_STATE = 0x03,
+	SCPI_CMD_GET_CSS_POWER_STATE = 0x04,
 	SCPI_CMD_SYS_POWER_STATE = 0x05
 } scpi_command_t;
 
+/*
+ * Macros to parse SCP response to GET_CSS_POWER_STATE command
+ *
+ *   [3:0] : cluster ID
+ *   [7:4] : cluster state: 0 = on; 3 = off; rest are reserved
+ *   [15:8]: on/off state for individual CPUs in the cluster
+ *
+ * Payload is in little-endian
+ */
+#define CLUSTER_ID(_resp)		((_resp) & 0xf)
+#define CLUSTER_POWER_STATE(_resp)	(((_resp) >> 4) & 0xf)
+
+/* Result is a bit mask of CPU on/off states in the cluster */
+#define CPU_POWER_STATE(_resp)		(((_resp) >> 8) & 0xff)
+
+/*
+ * For GET_CSS_POWER_STATE, SCP returns the power states of every cluster. The
+ * size of response depends on the number of clusters in the system. The
+ * SCP-to-AP payload contains 2 bytes per cluster. Make sure the response is
+ * large enough to contain power states of a given cluster
+ */
+#define CHECK_RESPONSE(_resp, _clus) \
+	(_resp.size >= (((_clus) + 1) * 2))
+
 typedef enum {
 	scpi_power_on = 0,
 	scpi_power_retention = 1,
@@ -101,6 +126,8 @@ extern void scpi_set_css_power_state(unsigned mpidr,
 					scpi_power_state_t cpu_state,
 					scpi_power_state_t cluster_state,
 					scpi_power_state_t css_state);
+int scpi_get_css_power_state(unsigned int mpidr, unsigned int *cpu_state_p,
+		unsigned int *cluster_state_p);
 uint32_t scpi_sys_power_state(scpi_system_state_t system_state);