tests: pm: Add a power domain test

Add simple test to exercise power domain behavior
with device runtime.

Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
diff --git a/tests/subsys/pm/power_domain/CMakeLists.txt b/tests/subsys/pm/power_domain/CMakeLists.txt
new file mode 100644
index 0000000..64fc937
--- /dev/null
+++ b/tests/subsys/pm/power_domain/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2021 Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(power_domain)
+
+target_sources(app PRIVATE src/main.c)
diff --git a/tests/subsys/pm/power_domain/app.overlay b/tests/subsys/pm/power_domain/app.overlay
new file mode 100644
index 0000000..186f49a
--- /dev/null
+++ b/tests/subsys/pm/power_domain/app.overlay
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/ {
+	test_domain: test_domain {
+		compatible = "power-domain";
+		status = "okay";
+	};
+
+	test_dev_a: test_dev_a {
+		compatible = "test-device-pm";
+		status = "okay";
+		power-domain = <&test_domain>;
+	};
+
+	test_dev_b: test_dev_b {
+		compatible = "test-device-pm";
+		status = "okay";
+		power-domain = <&test_domain>;
+	};
+};
diff --git a/tests/subsys/pm/power_domain/dts/bindings/test-device-pm.yaml b/tests/subsys/pm/power_domain/dts/bindings/test-device-pm.yaml
new file mode 100644
index 0000000..e52afe3
--- /dev/null
+++ b/tests/subsys/pm/power_domain/dts/bindings/test-device-pm.yaml
@@ -0,0 +1,10 @@
+# Copyright (c) 2021, Intel Corporation
+# SPDX-License-Identifier: Apache-2.0
+
+include: [base.yaml, pm.yaml]
+
+description: |
+    This binding provides resources required to build and run the
+    tests/subsys/pm/power_domain test in Zephyr.
+
+compatible: "test-device-pm"
diff --git a/tests/subsys/pm/power_domain/prj.conf b/tests/subsys/pm/power_domain/prj.conf
new file mode 100644
index 0000000..0aa49a2
--- /dev/null
+++ b/tests/subsys/pm/power_domain/prj.conf
@@ -0,0 +1,6 @@
+CONFIG_ZTEST=y
+CONFIG_PM=y
+CONFIG_PM_DEVICE=y
+CONFIG_POWER_DOMAIN=y
+CONFIG_PM_DEVICE_RUNTIME=y
+CONFIG_MP_NUM_CPUS=1
diff --git a/tests/subsys/pm/power_domain/src/main.c b/tests/subsys/pm/power_domain/src/main.c
new file mode 100644
index 0000000..f01e5ab
--- /dev/null
+++ b/tests/subsys/pm/power_domain/src/main.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2021 Intel Corporation.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <ztest.h>
+#include <pm/device.h>
+#include <pm/device_runtime.h>
+
+static int testing_domain_on_notitication;
+static int testing_domain_off_notitication;
+
+#define TEST_DOMAIN DT_NODELABEL(test_domain)
+#define TEST_DEVA DT_NODELABEL(test_dev_a)
+#define TEST_DEVB DT_NODELABEL(test_dev_b)
+
+static const struct device *domain, *deva, *devb;
+
+static int dev_init(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	return 0;
+}
+
+static int domain_pm_action(const struct device *dev,
+	enum pm_device_action action)
+{
+	int rc = 0;
+
+	switch (action) {
+	case PM_DEVICE_ACTION_RESUME:
+		/* Switch power on */
+		pm_device_children_action_run(dev, PM_DEVICE_ACTION_TURN_ON, NULL);
+		break;
+	case PM_DEVICE_ACTION_SUSPEND:
+		pm_device_children_action_run(dev, PM_DEVICE_ACTION_TURN_OFF, NULL);
+		break;
+	case PM_DEVICE_ACTION_TURN_ON:
+		__fallthrough;
+	case PM_DEVICE_ACTION_TURN_OFF:
+		break;
+	default:
+		rc = -ENOTSUP;
+	}
+
+	return rc;
+
+}
+
+static int deva_pm_action(const struct device *dev,
+		     enum pm_device_action pm_action)
+{
+	ARG_UNUSED(dev);
+
+	if (testing_domain_on_notitication > 0) {
+		if (pm_action == PM_DEVICE_ACTION_TURN_ON) {
+			testing_domain_on_notitication--;
+		}
+	} else if (testing_domain_off_notitication > 0) {
+		if (pm_action == PM_DEVICE_ACTION_TURN_OFF) {
+			testing_domain_off_notitication--;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Device B will return -ENOTSUP for TURN_ON and TURN_OFF actions.
+ * This way we can check if the subsystem properly handled its state.
+ */
+static int devb_pm_action(const struct device *dev,
+		     enum pm_device_action pm_action)
+{
+	int ret = 0;
+
+	ARG_UNUSED(dev);
+
+	if (testing_domain_on_notitication > 0) {
+		if (pm_action == PM_DEVICE_ACTION_TURN_ON) {
+			ret = -ENOTSUP;
+			testing_domain_on_notitication--;
+		}
+	} else if (testing_domain_off_notitication > 0) {
+		if (pm_action == PM_DEVICE_ACTION_TURN_OFF) {
+			ret = -ENOTSUP;
+			testing_domain_off_notitication--;
+		}
+	}
+
+	return ret;
+}
+
+
+PM_DEVICE_DT_DEFINE(TEST_DOMAIN, domain_pm_action);
+DEVICE_DT_DEFINE(TEST_DOMAIN, dev_init, PM_DEVICE_DT_GET(TEST_DOMAIN),
+		 NULL, NULL, POST_KERNEL, 10, NULL);
+
+PM_DEVICE_DT_DEFINE(TEST_DEVA, deva_pm_action);
+DEVICE_DT_DEFINE(TEST_DEVA, dev_init, PM_DEVICE_DT_GET(TEST_DEVA),
+		 NULL, NULL, POST_KERNEL, 20, NULL);
+
+PM_DEVICE_DT_DEFINE(TEST_DEVB, devb_pm_action);
+DEVICE_DT_DEFINE(TEST_DEVB, dev_init, PM_DEVICE_DT_GET(TEST_DEVB),
+		 NULL, NULL, POST_KERNEL, 30, NULL);
+
+/**
+ * @brief Test the power domain behavior
+ *
+ * Scenarios tested:
+ *
+ * - get + put multiple devices under a domain
+ * - notification when domain state changes
+ */
+static void test_power_domain_device_runtime(void)
+{
+	int ret;
+	enum pm_device_state state;
+
+	domain = DEVICE_DT_GET(TEST_DOMAIN);
+	deva = DEVICE_DT_GET(TEST_DEVA);
+	devb = DEVICE_DT_GET(TEST_DEVB);
+
+	pm_device_runtime_init_suspended(domain);
+	pm_device_runtime_init_suspended(deva);
+	pm_device_runtime_init_suspended(devb);
+
+	pm_device_runtime_enable(domain);
+	pm_device_runtime_enable(deva);
+	pm_device_runtime_enable(devb);
+
+	/* At this point all devices should be SUSPENDED */
+	pm_device_state_get(domain, &state);
+	zassert_equal(state, PM_DEVICE_STATE_SUSPENDED, NULL);
+
+	pm_device_state_get(deva, &state);
+	zassert_equal(state, PM_DEVICE_STATE_SUSPENDED, NULL);
+
+	pm_device_state_get(devb, &state);
+	zassert_equal(state, PM_DEVICE_STATE_SUSPENDED, NULL);
+
+	/* Now test if "get" a device will resume the domain */
+	ret = pm_device_runtime_get(deva);
+	zassert_equal(ret, 0, NULL);
+
+	pm_device_state_get(deva, &state);
+	zassert_equal(state, PM_DEVICE_STATE_ACTIVE, NULL);
+
+	pm_device_state_get(domain, &state);
+	zassert_equal(state, PM_DEVICE_STATE_ACTIVE, NULL);
+
+	ret = pm_device_runtime_get(devb);
+	zassert_equal(ret, 0, NULL);
+
+	ret = pm_device_runtime_put(deva);
+	zassert_equal(ret, 0, NULL);
+
+	/*
+	 * The domain has to still be active since device B
+	 * is still in use.
+	 */
+	pm_device_state_get(domain, &state);
+	zassert_equal(state, PM_DEVICE_STATE_ACTIVE, NULL);
+
+	/*
+	 * Now the domain should be suspended since there is no
+	 * one using it.
+	 */
+	ret = pm_device_runtime_put(devb);
+	zassert_equal(ret, 0, NULL);
+
+	pm_device_state_get(domain, &state);
+	zassert_equal(state, PM_DEVICE_STATE_SUSPENDED, NULL);
+
+	/*
+	 * With the domain suspended the device state should be OFF, since
+	 * the power was completely cut.
+	 */
+	pm_device_state_get(devb, &state);
+	zassert_equal(state, PM_DEVICE_STATE_OFF, NULL);
+
+	pm_device_state_get(deva, &state);
+	zassert_equal(state, PM_DEVICE_STATE_OFF, NULL);
+
+	/*
+	 * Now lets test that devices are notified when the domain
+	 * changes its state.
+	 */
+
+	/* Three devices has to get the notification */
+	testing_domain_on_notitication = 2;
+	ret = pm_device_runtime_get(domain);
+	zassert_equal(ret, 0, NULL);
+
+	zassert_equal(testing_domain_on_notitication, 0, NULL);
+
+	testing_domain_off_notitication = 2;
+	ret = pm_device_runtime_put(domain);
+	zassert_equal(ret, 0, NULL);
+
+	zassert_equal(testing_domain_off_notitication, 0, NULL);
+}
+
+void test_main(void)
+{
+	ztest_test_suite(power_domain_test,
+			 ztest_1cpu_unit_test(test_power_domain_device_runtime));
+
+	ztest_run_test_suite(power_domain_test);
+}
diff --git a/tests/subsys/pm/power_domain/testcase.yaml b/tests/subsys/pm/power_domain/testcase.yaml
new file mode 100644
index 0000000..811dcf4
--- /dev/null
+++ b/tests/subsys/pm/power_domain/testcase.yaml
@@ -0,0 +1,4 @@
+tests:
+  subsys.pm.power_domain:
+    platform_allow: native_posix
+    tags: pm