// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright 2025 Google, Inc.
 */

#include <linux/cleanup.h>
#include <linux/pm_runtime.h>
#include <kunit/device.h>
#include <kunit/test.h>

#define DEVICE_NAME "pm_runtime_test_device"

static void pm_runtime_depth_test(struct kunit *test)
{
	struct device *dev = kunit_device_register(test, DEVICE_NAME);

	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);

	pm_runtime_enable(dev);

	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
	KUNIT_EXPECT_EQ(test, 1, pm_runtime_get_sync(dev)); /* "already active" */
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}

/* Test pm_runtime_put() and friends when already suspended. */
static void pm_runtime_already_suspended_test(struct kunit *test)
{
	struct device *dev = kunit_device_register(test, DEVICE_NAME);

	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);

	pm_runtime_enable(dev);
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));

	pm_runtime_get_noresume(dev);
	KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync(dev));

	KUNIT_EXPECT_EQ(test, 1, pm_runtime_suspend(dev));
	KUNIT_EXPECT_EQ(test, 1, pm_runtime_autosuspend(dev));
	KUNIT_EXPECT_EQ(test, 1, pm_request_autosuspend(dev));

	pm_runtime_get_noresume(dev);
	KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));

	pm_runtime_get_noresume(dev);
	pm_runtime_put_autosuspend(dev);

	/* Grab 2 refcounts */
	pm_runtime_get_noresume(dev);
	pm_runtime_get_noresume(dev);
	/* The first put() sees usage_count 1 */
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync_autosuspend(dev));
	/* The second put() sees usage_count 0 but tells us "already suspended". */
	KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));

	/* Should have remained suspended the whole time. */
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}

static void pm_runtime_idle_test(struct kunit *test)
{
	struct device *dev = kunit_device_register(test, DEVICE_NAME);

	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);

	pm_runtime_enable(dev);

	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_idle(dev));
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
	pm_runtime_put_noidle(dev);
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_idle(dev));
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_idle(dev));
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_request_idle(dev));
}

static void pm_runtime_disabled_test(struct kunit *test)
{
	struct device *dev = kunit_device_register(test, DEVICE_NAME);

	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);

	/* Never called pm_runtime_enable() */
	KUNIT_EXPECT_FALSE(test, pm_runtime_enabled(dev));

	/* "disabled" is treated as "active" */
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
	KUNIT_EXPECT_FALSE(test, pm_runtime_suspended(dev));

	/*
	 * Note: these "fail", but they still acquire/release refcounts, so
	 * keep them balanced.
	 */
	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_get(dev));
	pm_runtime_put(dev);

	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_get_sync(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_put_sync(dev));

	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_get(dev));
	pm_runtime_put_autosuspend(dev);

	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_resume_and_get(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_idle(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_request_idle(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_request_resume(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_request_autosuspend(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_suspend(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_resume(dev));
	KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_autosuspend(dev));

	/* Still disabled */
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
	KUNIT_EXPECT_FALSE(test, pm_runtime_enabled(dev));
}

static void pm_runtime_error_test(struct kunit *test)
{
	struct device *dev = kunit_device_register(test, DEVICE_NAME);

	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);

	pm_runtime_enable(dev);
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));

	/* Fake a .runtime_resume() error */
	dev->power.runtime_error = -EIO;

	/*
	 * Note: these "fail", but they still acquire/release refcounts, so
	 * keep them balanced.
	 */
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get(dev));
	pm_runtime_put(dev);

	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get_sync(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_sync(dev));

	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get(dev));
	pm_runtime_put_autosuspend(dev);

	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_sync_autosuspend(dev));

	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_resume_and_get(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_idle(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_request_idle(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_request_resume(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_request_autosuspend(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_suspend(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_resume(dev));
	KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_autosuspend(dev));

	/* Error is still pending */
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
	KUNIT_EXPECT_EQ(test, -EIO, dev->power.runtime_error);
	/* Clear error */
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_set_suspended(dev));
	KUNIT_EXPECT_EQ(test, 0, dev->power.runtime_error);
	/* Still suspended */
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));

	KUNIT_EXPECT_EQ(test, 0, pm_runtime_get(dev));
	pm_runtime_barrier(dev);
	pm_runtime_put(dev);
	pm_runtime_suspend(dev); /* flush the put(), to suspend */
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));

	KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));

	KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
	pm_runtime_put_autosuspend(dev);

	KUNIT_EXPECT_EQ(test, 0, pm_runtime_resume_and_get(dev));

	/*
	 * The following should all return -EAGAIN (usage is non-zero) or 1
	 * (already resumed).
	 */
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_idle(dev));
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_request_idle(dev));
	KUNIT_EXPECT_EQ(test, 1, pm_request_resume(dev));
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_request_autosuspend(dev));
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_suspend(dev));
	KUNIT_EXPECT_EQ(test, 1, pm_runtime_resume(dev));
	KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_autosuspend(dev));

	KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));

	/* Suspended again */
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}

/*
 * Explore a typical probe() sequence in which a device marks itself powered,
 * but doesn't hold any runtime PM reference, so it suspends as soon as it goes
 * idle.
 */
static void pm_runtime_probe_active_test(struct kunit *test)
{
	struct device *dev = kunit_device_register(test, DEVICE_NAME);

	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);

	KUNIT_EXPECT_TRUE(test, pm_runtime_status_suspended(dev));

	KUNIT_EXPECT_EQ(test, 0, pm_runtime_set_active(dev));
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));

	pm_runtime_enable(dev);
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));

	/* Nothing to flush. We stay active. */
	pm_runtime_barrier(dev);
	KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));

	/* Ask for idle? Now we suspend. */
	KUNIT_EXPECT_EQ(test, 0, pm_runtime_idle(dev));
	KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}

static struct kunit_case pm_runtime_test_cases[] = {
	KUNIT_CASE(pm_runtime_depth_test),
	KUNIT_CASE(pm_runtime_already_suspended_test),
	KUNIT_CASE(pm_runtime_idle_test),
	KUNIT_CASE(pm_runtime_disabled_test),
	KUNIT_CASE(pm_runtime_error_test),
	KUNIT_CASE(pm_runtime_probe_active_test),
	{}
};

static struct kunit_suite pm_runtime_test_suite = {
	.name = "pm_runtime_test_cases",
	.test_cases = pm_runtime_test_cases,
};

kunit_test_suite(pm_runtime_test_suite);
MODULE_DESCRIPTION("Runtime power management unit test suite");
MODULE_LICENSE("GPL");
