diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 3f14dffb9669..2b39453e9dd1 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -40,6 +40,18 @@ config ARM_SCMI_POWER_DOMAIN
 	  will be called scmi_pm_domain. Note this may needed early in boot
 	  before rootfs may be available.
 
+config ARM_SCMI_POWER_CONTROL
+	bool "SCMI system power control driver"
+	depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+	default n
+	help
+	  This enables System Power control logic which binds system shutdown or
+	  reboot actions to SCMI System Power notifications generated by SCP
+	  firmware.
+
+	  Graceful requests' methods and timeout and can be configured using
+	  a few available module parameters.
+
 config ARM_SCPI_PROTOCOL
 	tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
 	depends on ARM || ARM64 || COMPILE_TEST
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 6a2ef63306d6..ddddb4636ebd 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -9,3 +9,4 @@ scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \
 		    $(scmi-transport-y)
 obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o
 obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
+obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) += scmi_power_control.o
diff --git a/drivers/firmware/arm_scmi/scmi_power_control.c b/drivers/firmware/arm_scmi/scmi_power_control.c
new file mode 100644
index 000000000000..216c2f031111
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_power_control.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI Generic SystemPower Control driver.
+ *
+ * Copyright (C) 2020-2021 ARM Ltd.
+ */
+/**
+ * DOC: Theory of operation
+ *
+ * In order to handle platform originated SCMI SystemPower requests (like
+ * shutdowns or cold/warm resets) we register an SCMI Notification notifier
+ * block to react when such SCMI SystemPower events are emitted.
+ *
+ * Once such a notification is received we act accordingly to perform the
+ * required system transition depending on the kind of request.
+ *
+ * By default graceful requests are routed to userspace through the same
+ * API methods (orderly_poweroff/reboot()) used by ACPI when handling ACPI
+ * Shutdown bus events: alternatively, by properly setting a couple of module
+ * parameters (scmi_graceful_*_signum), it is possible to choose to use signals
+ * to CAD pid as a mean to communicate such graceful requests to userspace.
+ *
+ * Forceful requests, instead, will simply cause an immediate emergency_sync()
+ * and subsequent Kernel-only shutdown/reboot.
+ *
+ * Additionally, setting scmi_graceful_request_tmo_ms to a non-zero value, it
+ * is possible to convert a timed-out graceful request to a forceful one.
+ *
+ * The assumption here is that even graceful requests can be upper-bound by a
+ * maximum final timeout strictly enforced by the platform itself which can
+ * ultimately cut the power off at will anytime: in order to avoid such extreme
+ * scenario, when scmi_graceful_request_tmo_ms is non-zero, we track progress of
+ * graceful requests through the means of a reboot notifier converting timed-out
+ * graceful requests to forceful ones: in such a way at least we perform a
+ * clean sync and shutdown/restart before the power is cut.
+ *
+ * Given that such platform hard-timeout, if present, is anyway highly platform
+ * and event specific and not exposed at run-time by the SCMI SytemPower
+ * protocol, the parameter scmi_graceful_request_tmo_ms defaults to zero.
+ */
+
+#define pr_fmt(fmt) "SCMI SystemPower - " fmt
+
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/reboot.h>
+#include <linux/sched/signal.h>
+#include <linux/scmi_protocol.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+enum scmi_syspower_state {
+	SCMI_SYSPOWER_IDLE,
+	SCMI_SYSPOWER_IN_PROGRESS,
+	SCMI_SYSPOWER_REBOOTING
+};
+
+static enum scmi_syspower_state scmi_state;
+/* Protect access to scmi_state */
+static DEFINE_MUTEX(scmi_state_mtx);
+
+static enum scmi_system_events scmi_required_transition = SCMI_SYSTEM_MAX;
+
+static unsigned int scmi_graceful_poweroff_signum;
+static unsigned int scmi_graceful_reboot_signum;
+static unsigned int scmi_graceful_request_tmo_ms;
+
+static struct timer_list scmi_request_timer;
+static struct work_struct scmi_forceful_work;
+
+/**
+ * scmi_reboot_notifier  - A reboot notifier to catch an ongoing successful
+ * system transition
+ * @nb: Reference to the related notifier block
+ * @reason: The reason for the ongoing reboot
+ * @__unused: The cmd being executed on a restart request (unused)
+ *
+ * When an ongoing system transition is detected, compatible with the one
+ * requested by SCMI, cancel the timer work.
+ * This is registered only when a valid SCMI SystemPower transition has been
+ * received and scmi_graceful_request_tmo_ms was non-zero.
+ *
+ * Return: NOTIFY_OK in any case
+ */
+static int scmi_reboot_notifier(struct notifier_block *nb,
+				unsigned long reason, void *__unused)
+{
+	mutex_lock(&scmi_state_mtx);
+	switch (reason) {
+	case SYS_HALT:
+	case SYS_POWER_OFF:
+		if (scmi_required_transition == SCMI_SYSTEM_SHUTDOWN)
+			scmi_state = SCMI_SYSPOWER_REBOOTING;
+		break;
+	case SYS_RESTART:
+		if (scmi_required_transition == SCMI_SYSTEM_COLDRESET ||
+		    scmi_required_transition == SCMI_SYSTEM_WARMRESET)
+			scmi_state = SCMI_SYSPOWER_REBOOTING;
+		break;
+	default:
+		break;
+	}
+
+	if (scmi_state == SCMI_SYSPOWER_REBOOTING) {
+		pr_debug("Reboot in progress...cancel timer.\n");
+		del_timer_sync(&scmi_request_timer);
+	}
+	mutex_unlock(&scmi_state_mtx);
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block scmi_reboot_nb = {
+	.notifier_call = &scmi_reboot_notifier,
+};
+
+static void scmi_request_forceful_transition(void);
+
+static void scmi_forceful_work_func(struct work_struct *work)
+{
+	if (system_state > SYSTEM_RUNNING)
+		return;
+
+	/* avoid deadlock unregistering reboot notifier */
+	unregister_reboot_notifier(&scmi_reboot_nb);
+
+	mutex_lock(&scmi_state_mtx);
+	if (scmi_state == SCMI_SYSPOWER_IN_PROGRESS)
+		scmi_request_forceful_transition();
+	mutex_unlock(&scmi_state_mtx);
+}
+
+/**
+ * scmi_request_timeout  - On timeout trigger a forceful transition
+ * @t: The timer reference
+ *
+ * Actual work is deferred out of the timer IRQ context since shutdown/reboot
+ * code do not play well when !in_task().
+ */
+static void scmi_request_timeout(struct timer_list *t)
+{
+	pr_debug("Graceful request timed out...forcing !\n");
+
+	schedule_work(&scmi_forceful_work);
+}
+
+/**
+ * scmi_request_graceful_transition  - Request graceful SystemPower transition
+ *
+ * Initiates the required SystemPower transition, requesting userspace
+ * co-operation: by default it uses the same orderly_ methods used by ACPI
+ * Shutdown event processing, while instead it uses a signal to CAD pid if
+ * any of the scmi_graceful_poweroff_ module params were non-zero.
+ *
+ * If scmi_graceful_request_tmo_ms is non zero, take care also to register a
+ * reboot notifier and a timer callback in order to detect if userspace actions
+ * are taking too long and in such a case fall back to a forceful transition.
+ */
+static void scmi_request_graceful_transition(void)
+{
+	if (scmi_graceful_request_tmo_ms) {
+		int ret;
+
+		ret = register_reboot_notifier(&scmi_reboot_nb);
+		if (!ret) {
+			INIT_WORK(&scmi_forceful_work, scmi_forceful_work_func);
+			scmi_request_timer.expires = jiffies +
+				msecs_to_jiffies(scmi_graceful_request_tmo_ms);
+			timer_setup(&scmi_request_timer,
+				    scmi_request_timeout, 0);
+			add_timer(&scmi_request_timer);
+		} else {
+			/* Carry on best effort even without a reboot notifier */
+			pr_warn("Cannot register reboot notifier !\n");
+		}
+	}
+
+	pr_debug("Serving graceful request: %d (timeout_ms:%d)\n",
+		 scmi_required_transition, scmi_graceful_request_tmo_ms);
+
+	switch (scmi_required_transition) {
+	case SCMI_SYSTEM_SHUTDOWN:
+		/*
+		 * When received early at boot-time the 'orderly' branch will
+		 * fail due to the lack of userspace itself, but the force=true
+		 * argument will anyway enable us to trigger a successful forced
+		 * shutdown.
+		 */
+		if (!scmi_graceful_poweroff_signum)
+			orderly_poweroff(true);
+		else
+			kill_cad_pid(scmi_graceful_poweroff_signum, 1);
+		break;
+	case SCMI_SYSTEM_COLDRESET:
+	case SCMI_SYSTEM_WARMRESET:
+		if (!scmi_graceful_reboot_signum)
+			orderly_reboot();
+		else
+			kill_cad_pid(scmi_graceful_reboot_signum, 1);
+		break;
+	default:
+		break;
+	}
+}
+
+/**
+ * scmi_request_forceful_transition  - Request forceful SystemPower transition
+ *
+ * Initiates the required SystemPower transition without involving userspace:
+ * just trigger the action at the kernel level after issuing an emergency sync.
+ */
+static void scmi_request_forceful_transition(void)
+{
+	pr_debug("Serving forceful request:%d\n", scmi_required_transition);
+
+	emergency_sync();
+	switch (scmi_required_transition) {
+	case SCMI_SYSTEM_SHUTDOWN:
+		kernel_power_off();
+		break;
+	case SCMI_SYSTEM_COLDRESET:
+	case SCMI_SYSTEM_WARMRESET:
+		kernel_restart(NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+/**
+ * scmi_userspace_notifier  - Notifier callback to act on SystemPower
+ * Notifications
+ * @nb: Reference to the related notifier block
+ * @event: The SystemPower notification event id
+ * @data: The SystemPower event report
+ *
+ * This callback is in charge of decoding the received SystemPower report
+ * and act accordingly triggering a graceful or forceful system transition.
+ *
+ * Note that once a valid SCMI SystemPower event starts being served, any
+ * other following SystemPower notification received from the same or any
+ * other SCMI instance (handle) will be ignored.
+ *
+ * Return: NOTIFY_OK once a valid SystemPower event has been successfully
+ * processed.
+ */
+static int scmi_userspace_notifier(struct notifier_block *nb,
+				   unsigned long event, void *data)
+{
+	struct scmi_system_power_state_notifier_report *er = data;
+
+	if (er->system_state >= SCMI_SYSTEM_POWERUP) {
+		pr_err("Ignoring invalid request: %d\n", er->system_state);
+		return NOTIFY_DONE;
+	}
+
+	/*
+	 * Bail out if system is already shutting down or an SCMI SystemPower
+	 * requested is already being served.
+	 */
+	if (system_state > SYSTEM_RUNNING)
+		return NOTIFY_DONE;
+	mutex_lock(&scmi_state_mtx);
+	if (scmi_state != SCMI_SYSPOWER_IDLE) {
+		pr_debug("Transition already in progress...ignore.\n");
+		mutex_unlock(&scmi_state_mtx);
+		return NOTIFY_DONE;
+	}
+	scmi_state = SCMI_SYSPOWER_IN_PROGRESS;
+	mutex_unlock(&scmi_state_mtx);
+
+	scmi_required_transition = er->system_state;
+
+	/* Leaving a trace in logs of who triggered the shutdown/reboot. */
+	pr_info("Serving shutdown/reboot request: %d\n",
+		scmi_required_transition);
+
+	if (SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(er->flags))
+		scmi_request_graceful_transition();
+	else
+		scmi_request_forceful_transition();
+
+	return NOTIFY_OK;
+}
+
+static int scmi_syspower_probe(struct scmi_device *sdev)
+{
+	int ret;
+	struct scmi_handle *handle = sdev->handle;
+	struct notifier_block *userspace_nb;
+
+	if (!handle)
+		return -ENODEV;
+
+	userspace_nb = devm_kzalloc(&sdev->dev, sizeof(*userspace_nb),
+				    GFP_KERNEL);
+	if (!userspace_nb)
+		return -ENOMEM;
+
+	userspace_nb->notifier_call = &scmi_userspace_notifier;
+	ret = handle->notify_ops->register_event_notifier(handle,
+						SCMI_PROTOCOL_SYSTEM,
+					SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,
+					    NULL, userspace_nb);
+	if (!ret)
+		dev_set_drvdata(&sdev->dev, userspace_nb);
+
+	return ret;
+}
+
+static void scmi_syspower_remove(struct scmi_device *sdev)
+{
+	struct notifier_block *userspace_nb;
+
+	userspace_nb = dev_get_drvdata(&sdev->dev);
+	if (userspace_nb) {
+		const struct scmi_handle *handle = sdev->handle;
+
+		handle->notify_ops->unregister_event_notifier(handle,
+							   SCMI_PROTOCOL_SYSTEM,
+					 SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,
+						      NULL, userspace_nb);
+	}
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+	{ SCMI_PROTOCOL_SYSTEM, "syspower" },
+	{ },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_system_power_driver = {
+	.name = "scmi-system-power",
+	.probe = scmi_syspower_probe,
+	.remove = scmi_syspower_remove,
+	.id_table = scmi_id_table,
+};
+module_scmi_driver(scmi_system_power_driver);
+
+module_param(scmi_graceful_request_tmo_ms, uint, 0644);
+MODULE_PARM_DESC(scmi_graceful_request_tmo_ms,
+		 "Maximum time(ms) allowed to userspace for complying to the request. Unlimited if zero.");
+
+module_param(scmi_graceful_poweroff_signum, uint, 0644);
+MODULE_PARM_DESC(scmi_graceful_poweroff_signum,
+		 "Signal to request graceful poweroff to CAD process. Ignored if zero.");
+
+module_param(scmi_graceful_reboot_signum, uint, 0644);
+MODULE_PARM_DESC(scmi_graceful_reboot_signum,
+		 "Signal to request graceful reboot to CAD process. Ignored if zero.");
+
+MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver");
+MODULE_LICENSE("GPL v2");
