power: domain: add SCMI driver

Add power domain driver based on SCMI power domain management protocol.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
diff --git a/drivers/firmware/scmi/scmi_agent-uclass.c b/drivers/firmware/scmi/scmi_agent-uclass.c
index 6f585b9..0f1003e 100644
--- a/drivers/firmware/scmi/scmi_agent-uclass.c
+++ b/drivers/firmware/scmi/scmi_agent-uclass.c
@@ -86,6 +86,9 @@
 	case SCMI_PROTOCOL_ID_BASE:
 		proto = priv->base_dev;
 		break;
+	case SCMI_PROTOCOL_ID_POWER_DOMAIN:
+		proto = priv->pwdom_dev;
+		break;
 	case SCMI_PROTOCOL_ID_CLOCK:
 		proto = priv->clock_dev;
 		break;
@@ -133,6 +136,9 @@
 	case SCMI_PROTOCOL_ID_BASE:
 		priv->base_dev = proto;
 		break;
+	case SCMI_PROTOCOL_ID_POWER_DOMAIN:
+		priv->pwdom_dev = proto;
+		break;
 	case SCMI_PROTOCOL_ID_CLOCK:
 		priv->clock_dev = proto;
 		break;
@@ -405,6 +411,11 @@
 		drv = NULL;
 		name = ofnode_get_name(node);
 		switch (protocol_id) {
+		case SCMI_PROTOCOL_ID_POWER_DOMAIN:
+			if (CONFIG_IS_ENABLED(SCMI_POWER_DOMAIN) &&
+			    scmi_protocol_is_supported(dev, protocol_id))
+				drv = DM_DRIVER_GET(scmi_power_domain);
+			break;
 		case SCMI_PROTOCOL_ID_CLOCK:
 			if (CONFIG_IS_ENABLED(CLK_SCMI) &&
 			    scmi_protocol_is_supported(dev, protocol_id))
diff --git a/drivers/power/domain/Kconfig b/drivers/power/domain/Kconfig
index 411c210..bd82d2f 100644
--- a/drivers/power/domain/Kconfig
+++ b/drivers/power/domain/Kconfig
@@ -83,6 +83,13 @@
 	  simply accepts requests to power on/off various HW modules without
 	  actually doing anything beyond a little error checking.
 
+config SCMI_POWER_DOMAIN
+	bool "Enable SCMI power domain driver"
+	depends on POWER_DOMAIN && SCMI_FIRMWARE
+	help
+	  Enable power domain implementation based on SCMI power domain
+	  management protocol.
+
 config TEGRA186_POWER_DOMAIN
 	bool "Enable Tegra186 BPMP-based power domain driver"
 	depends on TEGRA186_BPMP
diff --git a/drivers/power/domain/Makefile b/drivers/power/domain/Makefile
index aa5a4ba..2daab73 100644
--- a/drivers/power/domain/Makefile
+++ b/drivers/power/domain/Makefile
@@ -15,6 +15,7 @@
 obj-$(CONFIG_MESON_SECURE_POWER_DOMAIN) += meson-secure-pwrc.o
 obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain.o
 obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain-test.o
+obj-$(CONFIG_SCMI_POWER_DOMAIN) += scmi-power-domain.o
 obj-$(CONFIG_TEGRA186_POWER_DOMAIN) += tegra186-power-domain.o
 obj-$(CONFIG_TI_SCI_POWER_DOMAIN) += ti-sci-power-domain.o
 obj-$(CONFIG_TI_POWER_DOMAIN) += ti-power-domain.o
diff --git a/drivers/power/domain/scmi-power-domain.c b/drivers/power/domain/scmi-power-domain.c
new file mode 100644
index 0000000..3cd0f07
--- /dev/null
+++ b/drivers/power/domain/scmi-power-domain.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SCMI Power domain driver
+ *
+ * Copyright (C) 2023 Linaro Limited
+ *              author: AKASHI Takahiro <takahiro.akashi@linaro.org>
+ */
+
+#include <dm.h>
+#include <malloc.h>
+#include <power-domain.h>
+#include <power-domain-uclass.h>
+#include <scmi_agent.h>
+#include <scmi_protocols.h>
+#include <dm/device_compat.h>
+
+/**
+ * struct scmi_pwd_properties
+ * @attributes:	Power domain attributes
+ * @name:	Name of the domain
+ */
+struct scmi_pwd_properties {
+	u32 attributes;
+	u8 *name; /* not used now */
+};
+
+/**
+ * struct scmi_power_domain_priv
+ * @num_pwdoms:	Number of power domains
+ * @prop:	Pointer to domain's properties
+ * @stats_addr:	Address of statistics memory region
+ * @stats_len:	Length of statistics memory region
+ */
+struct scmi_power_domain_priv {
+	int num_pwdoms;
+	struct scmi_pwd_properties *prop;
+	u64 stats_addr;
+	size_t stats_len;
+};
+
+/**
+ * async_is_supported - check asynchronous transition
+ * @attributes:	Power domain attributes
+ *
+ * Determine if the power transition can be done asynchronously.
+ *
+ * Return: true if supported, false if not
+ */
+static bool async_is_supported(u32 attributes)
+{
+	if (attributes & SCMI_PWD_ATTR_PSTATE_ASYNC)
+		return true;
+
+	/* TODO: check attributes && SCMI_PWD_ATTR_PSTATE_SYNC */
+	return false;
+}
+
+/**
+ * scmi_power_domain_on - Enable the power domain
+ * @power_domain:	Power domain
+ *
+ * Turn on the power domain.
+ *
+ * Return: 0 on success, error code on failure
+ */
+static int scmi_power_domain_on(struct power_domain *power_domain)
+{
+	struct scmi_power_domain_priv *priv = dev_get_priv(power_domain->dev);
+	u32 flags, pstate;
+	int ret;
+
+	if (power_domain->id > priv->num_pwdoms)
+		return -EINVAL;
+
+	if (async_is_supported(priv->prop[power_domain->id].attributes))
+		flags = SCMI_PWD_SET_FLAGS_ASYNC;
+	else
+		flags = 0;
+
+	/* ON */
+	pstate = 0;
+
+	ret = scmi_pwd_state_set(power_domain->dev, flags, power_domain->id,
+				 pstate);
+	if (ret) {
+		dev_err(power_domain->dev, "failed to set the state on (%d)\n",
+			ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * scmi_power_domain_off - Disable the power domain
+ * @power_domain:	Power domain
+ *
+ * Turn off the power domain.
+ *
+ * Return: 0 on success, error code on failure
+ */
+static int scmi_power_domain_off(struct power_domain *power_domain)
+{
+	struct scmi_power_domain_priv *priv = dev_get_priv(power_domain->dev);
+	u32 flags, pstate;
+	int ret;
+
+	if (power_domain->id > priv->num_pwdoms)
+		return -EINVAL;
+
+	if (async_is_supported(priv->prop[power_domain->id].attributes))
+		flags = SCMI_PWD_SET_FLAGS_ASYNC;
+	else
+		flags = 0;
+
+	/* OFF */
+	pstate = SCMI_PWD_PSTATE_TYPE_LOST;
+
+	ret = scmi_pwd_state_set(power_domain->dev, flags, power_domain->id,
+				 pstate);
+	if (ret) {
+		dev_err(power_domain->dev, "failed to set the state off (%d)\n",
+			ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * scmi_power_domain_probe - Probe the power domain
+ * @dev:	Power domain device
+ *
+ * Probe the power domain and initialize the properties.
+ *
+ * Return: 0 on success, error code on failure
+ */
+static int scmi_power_domain_probe(struct udevice *dev)
+{
+	struct scmi_power_domain_priv *priv = dev_get_priv(dev);
+	u32 version;
+	int i, ret;
+
+	ret = devm_scmi_of_get_channel(dev);
+	if (ret) {
+		dev_err(dev, "failed to get channel (%d)\n", ret);
+		return ret;
+	}
+
+	ret = scmi_generic_protocol_version(dev, SCMI_PROTOCOL_ID_POWER_DOMAIN,
+					    &version);
+
+	ret = scmi_pwd_protocol_attrs(dev, &priv->num_pwdoms, &priv->stats_addr,
+				      &priv->stats_len);
+	if (ret) {
+		dev_err(dev, "failed to get protocol attributes (%d)\n", ret);
+		return ret;
+	}
+
+	priv->prop = calloc(sizeof(*priv->prop), priv->num_pwdoms);
+	if (!priv->prop)
+		return -ENOMEM;
+
+	for (i = 0; i < priv->num_pwdoms; i++) {
+		ret = scmi_pwd_attrs(dev, i, &priv->prop[i].attributes,
+				     &priv->prop[i].name);
+		if (ret) {
+			dev_err(dev, "failed to get attributes pwd:%d (%d)\n",
+				i, ret);
+			for (i--; i >= 0; i--)
+				free(priv->prop[i].name);
+			free(priv->prop);
+
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+struct power_domain_ops scmi_power_domain_ops = {
+	.on = scmi_power_domain_on,
+	.off = scmi_power_domain_off,
+};
+
+U_BOOT_DRIVER(scmi_power_domain) = {
+	.name = "scmi_power_domain",
+	.id = UCLASS_POWER_DOMAIN,
+	.ops = &scmi_power_domain_ops,
+	.probe = scmi_power_domain_probe,
+	.priv_auto = sizeof(struct scmi_power_domain_priv),
+};
diff --git a/include/scmi_agent-uclass.h b/include/scmi_agent-uclass.h
index 35d9606..33e0e18 100644
--- a/include/scmi_agent-uclass.h
+++ b/include/scmi_agent-uclass.h
@@ -23,6 +23,7 @@
  * @agent_name:		Agent name
  * @agent_id:		Identifier of agent
  * @base_dev:		SCMI base protocol device
+ * @pwdom_dev:		SCMI power domain management protocol device
  * @clock_dev:		SCMI clock protocol device
  * @resetdom_dev:	SCMI reset domain protocol device
  * @voltagedom_dev:	SCMI voltage domain protocol device
@@ -38,6 +39,7 @@
 	u8 *agent_name;
 	u32 agent_id;
 	struct udevice *base_dev;
+	struct udevice *pwdom_dev;
 	struct udevice *clock_dev;
 	struct udevice *resetdom_dev;
 	struct udevice *voltagedom_dev;