diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/power/sequencing/Kconfig | 19 | ||||
| -rw-r--r-- | drivers/power/sequencing/Makefile | 2 | ||||
| -rw-r--r-- | drivers/power/sequencing/core.c | 49 | ||||
| -rw-r--r-- | drivers/power/sequencing/pwrseq-kunit.c | 1497 | ||||
| -rw-r--r-- | drivers/power/sequencing/pwrseq-pcie-m2.c | 45 | ||||
| -rw-r--r-- | drivers/power/sequencing/pwrseq-qcom-wcn.c | 30 | ||||
| -rw-r--r-- | drivers/power/sequencing/pwrseq-renesas-pwrrdy.c | 142 |
7 files changed, 1782 insertions, 2 deletions
diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 226c62704d9b..b6a9f25fe3e3 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -27,6 +27,15 @@ config POWER_SEQUENCING_QCOM_WCN this driver is needed for correct power control or else we'd risk not respecting the required delays between enabling Bluetooth and WLAN. +config POWER_SEQUENCING_RENESAS_PWRRDY + tristate "Renesas Power Ready sequencing driver" + depends on SYSC_RZ || COMPILE_TEST + select AUXILIARY_BUS + help + Say Y here to enable the power sequencing driver for the Renesas + Power Ready signals. This driver handles the power ready signals + required to power on the various IP's on RZ/G3L platform. + config POWER_SEQUENCING_TH1520_GPU tristate "T-HEAD TH1520 GPU power sequencing driver" depends on ARCH_THEAD || COMPILE_TEST @@ -47,4 +56,14 @@ config POWER_SEQUENCING_PCIE_M2 connectors. This driver handles the power sequencing for the M.2 connectors exposing multiple interfaces like PCIe, SATA, UART, etc... +config POWER_SEQUENCING_KUNIT + tristate "Build power sequencing KUnit test cases" + depends on KUNIT + default KUNIT_ALL_TESTS + help + Say Y here to build the module containing KUnit test cases for the + power sequencing subsystem. + + If unsure, say N. + endif diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile index 0911d4618298..76a2775fab33 100644 --- a/drivers/power/sequencing/Makefile +++ b/drivers/power/sequencing/Makefile @@ -4,5 +4,7 @@ obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o pwrseq-core-y := core.o obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o +obj-$(CONFIG_POWER_SEQUENCING_RENESAS_PWRRDY) += pwrseq-renesas-pwrrdy.o obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) += pwrseq-pcie-m2.o +obj-$(CONFIG_POWER_SEQUENCING_KUNIT) += pwrseq-kunit.o diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 3076b3879af9..d7a545bb4ed2 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -183,12 +183,15 @@ static void pwrseq_unit_release(struct kref *ref) * the state lock has been released. It's useful for implementing * boot-up delays without blocking other users from powering up * using the same power sequencer. + * @is_controllable: Optional callback reporting whether enabling/disabling + * this target actually controls power. */ struct pwrseq_target { struct list_head list; const char *name; struct pwrseq_unit *unit; pwrseq_power_state_func post_enable; + pwrseq_is_controllable_func is_controllable; }; static struct pwrseq_target * @@ -207,6 +210,7 @@ pwrseq_target_new(const struct pwrseq_target_data *data) } target->post_enable = data->post_enable; + target->is_controllable = data->is_controllable; return target; } @@ -994,6 +998,51 @@ struct device *pwrseq_to_device(struct pwrseq_desc *desc) } EXPORT_SYMBOL_GPL(pwrseq_to_device); +/** + * pwrseq_is_controllable() - Check whether the target provides a + * host-controllable power actuator. + * @desc: Descriptor referencing the power sequencer. + * + * Some power sequencing targets provide no host-controllable enable for their + * function on a given board, for instance when the enable line is not wired up + * and is instead hardwired to an always-on level. For such targets a call to + * pwrseq_power_off() is still allowed, so that the consumer can drop its vote + * on the (possibly shared) resources, but the host cannot gate the function + * on its own. + * + * Returns: + * True if the target provides a host-controllable power actuator, false + * otherwise. Also returns false if @desc is NULL. + */ +bool pwrseq_is_controllable(struct pwrseq_desc *desc) +{ + struct pwrseq_device *pwrseq; + struct pwrseq_target *target; + struct pwrseq_unit *unit; + + might_sleep(); + + if (!desc) + return false; + + pwrseq = desc->pwrseq; + target = desc->target; + unit = target->unit; + + guard(rwsem_read)(&pwrseq->rw_lock); + if (!device_is_registered(&pwrseq->dev)) + return false; + + if (!unit->enable && !unit->disable) + return false; + + if (!target->is_controllable) + return true; + + return target->is_controllable(pwrseq); +} +EXPORT_SYMBOL_GPL(pwrseq_is_controllable); + #if IS_ENABLED(CONFIG_DEBUG_FS) struct pwrseq_debugfs_count_ctx { diff --git a/drivers/power/sequencing/pwrseq-kunit.c b/drivers/power/sequencing/pwrseq-kunit.c new file mode 100644 index 000000000000..c2899f3b16e6 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-kunit.c @@ -0,0 +1,1497 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries + */ + +#include <linux/err.h> +#include <linux/fwnode.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/pwrseq/consumer.h> +#include <linux/pwrseq/provider.h> + +#include <kunit/fwnode.h> +#include <kunit/platform_device.h> +#include <kunit/resource.h> +#include <kunit/test.h> + +#define PWRSEQ_TEST_PARENT "pwrseq-test-parent" +#define PWRSEQ_TEST_CONSUMER "pwrseq-test-consumer" + +#define PWRSEQ_SWNODE_TEST_PROVIDER "pwrseq-swnode-test-provider" +#define PWRSEQ_SWNODE_TEST_PROVIDER_2 "pwrseq-swnode-test-provider-2" +#define PWRSEQ_PROBE_ORDER_TEST_CONSUMER "pwrseq-probe-order-test-consumer" +#define PWRSEQ_PROBE_DEFER_TEST_CONSUMER "pwrseq-probe-defer-test-consumer" + +static const struct software_node pwrseq_test_provider_swnode = { + .name = "pwrseq-test-provider", +}; + +KUNIT_DEFINE_ACTION_WRAPPER(pwrseq_device_unregister_wrapper, + pwrseq_device_unregister, + struct pwrseq_device *); + +static struct pwrseq_device * +kunit_pwrseq_device_register(struct kunit *test, + const struct pwrseq_config *config) +{ + struct pwrseq_device *pwrseq; + int ret; + + pwrseq = pwrseq_device_register(config); + if (IS_ERR(pwrseq)) + return pwrseq; + + ret = kunit_add_action_or_reset(test, pwrseq_device_unregister_wrapper, + pwrseq); + if (ret) + return ERR_PTR(ret); + + return pwrseq; +} + +KUNIT_DEFINE_ACTION_WRAPPER(pwrseq_put_wrapper, pwrseq_put, + struct pwrseq_desc *); + +static struct pwrseq_desc * +kunit_pwrseq_get(struct kunit *test, struct device *dev, const char *target) +{ + struct pwrseq_desc *desc; + int ret; + + desc = pwrseq_get(dev, target); + if (IS_ERR(desc)) + return desc; + + ret = kunit_add_action_or_reset(test, pwrseq_put_wrapper, desc); + if (ret) + return ERR_PTR(ret); + + return desc; +} + +struct pwrseq_test_ctx { + const char *consumer_name; + int enable_count_a; + int enable_count_b; + int disable_count_a; + int disable_count_b; + int enable_calls_a; + int post_enable_calls; + bool enable_error_a; + bool enable_error_b; + bool post_enable_error; +}; + +static int pwrseq_test_no_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + return PWRSEQ_NO_MATCH; +} + +static int pwrseq_test_match_by_name(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return strcmp(dev_name(dev), ctx->consumer_name) == 0 ? + PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; +} + +static int pwrseq_test_match_always(struct pwrseq_device *pwrseq, + struct device *dev) +{ + return PWRSEQ_MATCH_OK; +} + +static int pwrseq_test_enable_a(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + ctx->enable_calls_a++; + + if (ctx->enable_error_a) + return -EIO; + + ctx->enable_count_a++; + + return 0; +} + +static int pwrseq_test_disable_a(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + ctx->disable_count_a++; + + return 0; +} + +static int pwrseq_test_enable_b(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + if (ctx->enable_error_b) + return -EIO; + + ctx->enable_count_b++; + + return 0; +} + +static int pwrseq_test_disable_b(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + ctx->disable_count_b++; + + return 0; +} + +static int pwrseq_test_post_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + ctx->post_enable_calls++; + + return ctx->post_enable_error ? -EIO : 0; +} + +static int pwrseq_test_parent_probe(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver pwrseq_test_parent_driver = { + .probe = pwrseq_test_parent_probe, + .driver = { + .name = PWRSEQ_TEST_PARENT, + }, +}; + +static int pwrseq_test_parent_init(struct kunit *test) +{ + static const struct platform_device_info pdevinfo = { + .name = PWRSEQ_TEST_PARENT, + .id = PLATFORM_DEVID_NONE, + }; + + struct platform_device *pdev; + bool bound; + int ret; + + ret = kunit_platform_driver_register(test, &pwrseq_test_parent_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + test->priv = pdev; + + return 0; +} + +/* + * Test that registering a power sequencer whose unit dependency graph + * contains a two-node cycle (A -> B -> A) is rejected with -EINVAL. + */ +static void pwrseq_circular_deps(struct kunit *test) +{ + const struct pwrseq_unit_data *unit_a_deps[2] = { }; + const struct pwrseq_unit_data *unit_b_deps[2] = { }; + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct pwrseq_unit_data unit_a, unit_b; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit_a = (struct pwrseq_unit_data){ + .name = "unit-a", + .deps = unit_a_deps, + }; + + unit_b = (struct pwrseq_unit_data){ + .name = "unit-b", + .deps = unit_b_deps, + }; + + unit_a_deps[0] = &unit_b; + unit_b_deps[0] = &unit_a; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit_a, + }; + + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .match = pwrseq_test_no_match, + .targets = targets, + }; + + kunit_warning_suppress(test) { + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that a longer chain cycle (A -> B -> C -> D -> A) is also rejected + * with -EINVAL. + */ +static void pwrseq_circular_deps_chain(struct kunit *test) +{ + struct pwrseq_unit_data unit_a, unit_b, unit_c, unit_d; + const struct pwrseq_unit_data *unit_a_deps[2] = { }; + const struct pwrseq_unit_data *unit_b_deps[2] = { }; + const struct pwrseq_unit_data *unit_c_deps[2] = { }; + const struct pwrseq_unit_data *unit_d_deps[2] = { }; + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit_a = (struct pwrseq_unit_data){ + .name = "unit-a", + .deps = unit_a_deps, + }; + unit_b = (struct pwrseq_unit_data){ + .name = "unit-b", + .deps = unit_b_deps, + }; + unit_c = (struct pwrseq_unit_data){ + .name = "unit-c", + .deps = unit_c_deps, + }; + unit_d = (struct pwrseq_unit_data){ + .name = "unit-d", + .deps = unit_d_deps, + }; + + unit_a_deps[0] = &unit_b; + unit_b_deps[0] = &unit_c; + unit_c_deps[0] = &unit_d; + unit_d_deps[0] = &unit_a; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit_a, + }; + + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .match = pwrseq_test_no_match, + .targets = targets, + }; + + kunit_warning_suppress(test) { + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that a valid acyclic dependency graph (A -> B) registers successfully. + */ +static void pwrseq_register_valid(struct kunit *test) +{ + const struct pwrseq_unit_data *unit_a_deps[2] = { }; + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct pwrseq_unit_data unit_a, unit_b; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit_b = (struct pwrseq_unit_data){ + .name = "unit-b", + }; + + unit_a_deps[0] = &unit_b; + unit_a = (struct pwrseq_unit_data){ + .name = "unit-a", + .deps = unit_a_deps, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit_a, + }; + + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .match = pwrseq_test_no_match, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); +} + +/* + * Test that registration is rejected with -EINVAL when .parent, .match or + * .targets is missing, or when .targets is a non-NULL but empty array. + */ +static void pwrseq_register_invalid_args(struct kunit *test) +{ + const struct pwrseq_target_data *empty_targets[1] = { }; + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + unit = (struct pwrseq_unit_data){ .name = "unit-a" }; + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit, + }; + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .match = pwrseq_test_no_match, + .targets = targets, + }; + + config.parent = NULL; + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); + config.parent = &pdev->dev; + + config.match = NULL; + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); + config.match = pwrseq_test_no_match; + + config.targets = NULL; + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); + + config.targets = empty_targets; + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that a target without a unit is rejected with -EINVAL. + */ +static void pwrseq_register_target_without_unit(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = NULL, + }; + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .match = pwrseq_test_no_match, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); + KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); +} + +/* + * Test that enabling and disabling a single-unit target increments and + * decrements enable_count correctly and fires the enable/disable callbacks + * exactly once. + */ +static void pwrseq_enable_disable(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_test_ctx *ctx; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_desc *desc; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit = (struct pwrseq_unit_data){ + .name = "unit-a", + .enable = pwrseq_test_enable_a, + .disable = pwrseq_test_disable_a, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit, + }; + + targets[0] = ⌖ + + ctx->consumer_name = PWRSEQ_TEST_CONSUMER; + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = ctx, + .match = pwrseq_test_match_by_name, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); + + ret = pwrseq_disable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that two consumers sharing a common dependency unit cause it to be + * enabled only once and disabled only once, while enable_count tracks each + * consumer correctly. + */ +static void pwrseq_shared_deps(struct kunit *test) +{ + const struct pwrseq_unit_data *unit_a_deps[2] = { }; + const struct pwrseq_unit_data *unit_b_deps[2] = { }; + const struct pwrseq_target_data *targets[3] = { }; + struct pwrseq_unit_data dep_unit, unit_a, unit_b; + struct pwrseq_target_data target_a, target_b; + struct platform_device *parent = test->priv; + struct platform_device *pdev_a, *pdev_b; + struct platform_device_info pdevinfo; + struct pwrseq_desc *desc_a, *desc_b; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dep_unit = (struct pwrseq_unit_data){ + .name = "dep", + .enable = pwrseq_test_enable_a, + .disable = pwrseq_test_disable_a, + }; + + unit_a_deps[0] = &dep_unit; + unit_a = (struct pwrseq_unit_data){ + .name = "unit-a", + .deps = unit_a_deps, + .enable = pwrseq_test_enable_b, + .disable = pwrseq_test_disable_b, + }; + + unit_b_deps[0] = &dep_unit; + unit_b = (struct pwrseq_unit_data){ + .name = "unit-b", + .deps = unit_b_deps, + }; + + target_a = (struct pwrseq_target_data){ + .name = "target-a", + .unit = &unit_a, + }; + target_b = (struct pwrseq_target_data){ + .name = "target-b", + .unit = &unit_b, + }; + + targets[0] = &target_a; + targets[1] = &target_b; + + ctx->consumer_name = PWRSEQ_TEST_CONSUMER "-a"; + + config = (struct pwrseq_config){ + .parent = &parent->dev, + .drvdata = ctx, + .match = pwrseq_test_match_by_name, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER "-a", + .id = PLATFORM_DEVID_NONE, + }; + pdev_a = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev_a); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER "-b", + .id = PLATFORM_DEVID_NONE, + }; + pdev_b = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev_b); + + /* + * match_by_name() only matches one consumer name at a time, so + * reconfigure ctx->consumer_name between the two pwrseq_get() calls + * below to acquire descriptors for both consumer-a and consumer-b. + */ + desc_a = kunit_pwrseq_get(test, &pdev_a->dev, "target-a"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_a); + + ctx->consumer_name = PWRSEQ_TEST_CONSUMER "-b"; + desc_b = kunit_pwrseq_get(test, &pdev_b->dev, "target-b"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_b); + + ret = pwrseq_enable(desc_a); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 1); + + ret = pwrseq_enable(desc_b); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + ret = pwrseq_disable(desc_a); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_b, 1); + + ret = pwrseq_disable(desc_b); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that calling pwrseq_enable() twice on the same descriptor has no + * effect. + */ +static void pwrseq_enable_idempotent(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_unit_data unit; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit = (struct pwrseq_unit_data){ + .name = "unit-a", + .enable = pwrseq_test_enable_a, + .disable = pwrseq_test_disable_a, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit, + }; + + targets[0] = ⌖ + + ctx->consumer_name = PWRSEQ_TEST_CONSUMER; + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = ctx, + .match = pwrseq_test_match_by_name, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + /* Second power_on on same descriptor must be a no-op. */ + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + ret = pwrseq_disable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that when a dependency unit's enable() callback fails, the error + * propagates to the caller, the top unit's enable callback is never reached, + * and target->post_enable() is never invoked. + */ +static void pwrseq_enable_enable_error(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + const struct pwrseq_unit_data *top_deps[2] = { }; + struct pwrseq_unit_data dep_unit, top_unit; + struct platform_device *pdev = test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dep_unit = (struct pwrseq_unit_data){ + .name = "dep", + .enable = pwrseq_test_enable_a, + }; + + top_deps[0] = &dep_unit; + top_unit = (struct pwrseq_unit_data){ + .name = "top", + .deps = top_deps, + .enable = pwrseq_test_enable_b, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &top_unit, + .post_enable = pwrseq_test_post_enable, + }; + + targets[0] = ⌖ + + ctx->consumer_name = PWRSEQ_TEST_CONSUMER; + ctx->enable_error_a = true; + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = ctx, + .match = pwrseq_test_match_by_name, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + /* + * Dependency's enable() was attempted but failed, so it never + * incremented. + */ + KUNIT_EXPECT_EQ(test, ctx->enable_calls_a, 1); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 0); + /* Target unit's enable() was never reached. */ + KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 0); + /* Target's .post_enable() must not run on an unpowered sequencer. */ + KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 0); +} + +/* + * Test that when the top unit's own enable() callback fails after its + * dependency's enable() already succeeded, the dependency is rolled back. + */ +static void pwrseq_enable_rollback_on_top_failure(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + const struct pwrseq_unit_data *top_deps[2] = { }; + struct pwrseq_unit_data dep_unit, top_unit; + struct platform_device *pdev = test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + dep_unit = (struct pwrseq_unit_data){ + .name = "dep", + .enable = pwrseq_test_enable_a, + .disable = pwrseq_test_disable_a, + }; + + top_deps[0] = &dep_unit; + top_unit = (struct pwrseq_unit_data){ + .name = "top", + .deps = top_deps, + .enable = pwrseq_test_enable_b, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &top_unit, + }; + + targets[0] = ⌖ + + ctx->consumer_name = PWRSEQ_TEST_CONSUMER; + ctx->enable_error_b = true; + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = ctx, + .match = pwrseq_test_match_by_name, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + /* + * Dependency was enabled, then rolled back once top's own enable + * failed. + */ + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); + /* + * Target unit's own enable never incremented since it returned an + * error. + */ + KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 0); +} + +/* + * Test target->post_enable() on both the success and failure paths: it must + * run exactly once per pwrseq_enable() call that actually powers the target + * unit on, and a failing post_enable() must roll the unit back and clear + * powered_on so a subsequent pwrseq_enable() is called again. + */ +static void pwrseq_enable_post_enable(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_test_ctx *ctx; + struct pwrseq_desc *desc; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit = (struct pwrseq_unit_data){ + .name = "unit-a", + .enable = pwrseq_test_enable_a, + .disable = pwrseq_test_disable_a, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit, + .post_enable = pwrseq_test_post_enable, + }; + + targets[0] = ⌖ + + ctx->consumer_name = PWRSEQ_TEST_CONSUMER; + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = ctx, + .match = pwrseq_test_match_by_name, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + /* Success path: post_enable() runs once and reports success. */ + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 1); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); + + ret = pwrseq_disable(desc); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Failure path: post_enable() fails, unit is rolled back. */ + ctx->post_enable_error = true; + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 2); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 2); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 2); + + /* powered_on must have been cleared. */ + ret = pwrseq_enable(desc); + KUNIT_EXPECT_EQ(test, ret, -EIO); + KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 3); +} + +/* + * Test that pwrseq_get() returns -ENOENT when the matched provider does not + * have the requested target name. + */ +static void pwrseq_get_target_not_found(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_desc *desc; + + unit = (struct pwrseq_unit_data){ + .name = "unit-a", + }; + + target = (struct pwrseq_target_data){ + .name = "real-target", + .unit = &unit, + }; + + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .match = pwrseq_test_match_always, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc = kunit_pwrseq_get(test, &pdev->dev, "nonexistent-target"); + KUNIT_EXPECT_TRUE(test, IS_ERR(desc)); + KUNIT_EXPECT_EQ(test, PTR_ERR(desc), -ENOENT); +} + +/* + * Test that pwrseq_put() on a descriptor that is still powered on disables + * it as part of tear-down, instead of leaking the power-on state. + */ +static void pwrseq_put_disables_powered_desc(struct kunit *test) +{ + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device *pdev = test->priv; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_test_ctx *ctx; + struct pwrseq_config config; + struct pwrseq_desc *desc; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit = (struct pwrseq_unit_data){ + .name = "unit-a", + .enable = pwrseq_test_enable_a, + .disable = pwrseq_test_disable_a, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit, + }; + + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = ctx, + .match = pwrseq_test_match_always, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret = pwrseq_enable(desc); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); + + /* + * Run the deferred pwrseq_put() action now instead of at test + * teardown, so its effect can be asserted below. + */ + kunit_release_action(test, pwrseq_put_wrapper, desc); + KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); +} + +/* + * Test that pwrseq_device_unregister() on a target with an active user + * triggers "REMOVING POWER SEQUENCER WITH ACTIVE USERS", and that both + * pwrseq_enable() and pwrseq_disable() start returning -ENODEV for + * previously acquired descriptors afterwards. + */ +static void pwrseq_unregister_with_active_user(struct kunit *test) +{ + struct platform_device *pdev = test->priv, *cons_a, *cons_b; + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device_info pdevinfo; + struct pwrseq_desc *desc_a, *desc_b; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_test_ctx *ctx; + struct pwrseq_config config; + bool bound; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + unit = (struct pwrseq_unit_data){ + .name = "unit-a", + .enable = pwrseq_test_enable_a, + .disable = pwrseq_test_disable_a, + }; + + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit, + }; + + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = ctx, + .match = pwrseq_test_match_always, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = 0, + }; + cons_a = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_a); + + wait_for_device_probe(); + scoped_guard(device, &cons_a->dev) + bound = device_is_bound(&cons_a->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdevinfo.id = 1; + cons_b = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_b); + + wait_for_device_probe(); + scoped_guard(device, &cons_b->dev) + bound = device_is_bound(&cons_b->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + desc_a = kunit_pwrseq_get(test, &cons_a->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_a); + desc_b = kunit_pwrseq_get(test, &cons_b->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_b); + + ret = pwrseq_enable(desc_a); + KUNIT_ASSERT_EQ(test, ret, 0); + + kunit_warning_suppress(test) { + kunit_release_action(test, pwrseq_device_unregister_wrapper, pwrseq); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } + + ret = pwrseq_enable(desc_b); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); + + ret = pwrseq_disable(desc_a); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); +} + +/* + * Test that pwrseq_to_device() returns the pwrseq provider's own device, + * and NULL for a NULL descriptor. + */ +static void pwrseq_to_device_test(struct kunit *test) +{ + struct platform_device *parent = test->priv, *cons; + const struct pwrseq_target_data *targets[2] = { }; + struct platform_device_info pdevinfo; + struct pwrseq_target_data target; + struct pwrseq_unit_data unit; + struct pwrseq_device *pwrseq; + struct pwrseq_config config; + struct pwrseq_desc *desc; + struct device *dev; + + unit = (struct pwrseq_unit_data){ .name = "unit-a" }; + target = (struct pwrseq_target_data){ + .name = "test-target", + .unit = &unit, + }; + targets[0] = ⌖ + + config = (struct pwrseq_config){ + .parent = &parent->dev, + .match = pwrseq_test_match_always, + .targets = targets, + }; + + pwrseq = kunit_pwrseq_device_register(test, &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + desc = kunit_pwrseq_get(test, &cons->dev, "test-target"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + dev = pwrseq_to_device(desc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + KUNIT_EXPECT_PTR_EQ(test, dev->parent, &parent->dev); + KUNIT_EXPECT_TRUE(test, device_is_registered(dev)); + + KUNIT_EXPECT_PTR_EQ(test, pwrseq_to_device(NULL), NULL); +} + +static struct kunit_case pwrseq_tests[] = { + KUNIT_CASE(pwrseq_circular_deps), + KUNIT_CASE(pwrseq_circular_deps_chain), + KUNIT_CASE(pwrseq_register_valid), + KUNIT_CASE(pwrseq_register_invalid_args), + KUNIT_CASE(pwrseq_register_target_without_unit), + KUNIT_CASE(pwrseq_enable_disable), + KUNIT_CASE(pwrseq_shared_deps), + KUNIT_CASE(pwrseq_enable_idempotent), + KUNIT_CASE(pwrseq_enable_enable_error), + KUNIT_CASE(pwrseq_enable_rollback_on_top_failure), + KUNIT_CASE(pwrseq_enable_post_enable), + KUNIT_CASE(pwrseq_get_target_not_found), + KUNIT_CASE(pwrseq_put_disables_powered_desc), + KUNIT_CASE(pwrseq_unregister_with_active_user), + KUNIT_CASE(pwrseq_to_device_test), + { } +}; + +static struct kunit_suite pwrseq_test_suite = { + .name = "pwrseq", + .init = pwrseq_test_parent_init, + .test_cases = pwrseq_tests, +}; + +/* + * Match a consumer to this test provider by resolving its "pwrseq-provider" + * software-node reference and comparing it to the provider's own fwnode. + */ +static int pwrseq_test_match_by_swnode_ref(struct pwrseq_device *pwrseq, + struct device *dev) +{ + const struct software_node *provider_swnode; + struct fwnode_handle *provider_fwnode, *ref; + bool match; + + if (!dev_fwnode(dev)) + return PWRSEQ_NO_MATCH; + + ref = fwnode_find_reference(dev_fwnode(dev), "pwrseq-provider", 0); + if (IS_ERR_OR_NULL(ref)) + return PWRSEQ_NO_MATCH; + + provider_swnode = pwrseq_device_get_drvdata(pwrseq); + provider_fwnode = software_node_fwnode(provider_swnode); + match = (ref == provider_fwnode); + fwnode_handle_put(ref); + + return match ? PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; +} + +static const struct pwrseq_unit_data pwrseq_swnode_test_unit = { + .name = "test-target", +}; + +static const struct pwrseq_target_data pwrseq_swnode_test_target = { + .name = "test-target", + .unit = &pwrseq_swnode_test_unit, +}; + +static const struct pwrseq_target_data *pwrseq_swnode_test_targets[] = { + &pwrseq_swnode_test_target, + NULL, +}; + +struct pwrseq_swnode_provider_pdata { + const struct software_node *provider_swnode; +}; + +static int pwrseq_swnode_provider_probe(struct platform_device *pdev) +{ + const struct pwrseq_swnode_provider_pdata *pdata = dev_get_platdata(&pdev->dev); + struct pwrseq_config config; + + config = (struct pwrseq_config){ + .parent = &pdev->dev, + .drvdata = (void *)pdata->provider_swnode, + .match = pwrseq_test_match_by_swnode_ref, + .targets = pwrseq_swnode_test_targets, + }; + + return PTR_ERR_OR_ZERO(devm_pwrseq_device_register(&pdev->dev, &config)); +} + +static struct platform_driver pwrseq_swnode_provider_driver = { + .probe = pwrseq_swnode_provider_probe, + .driver = { + .name = PWRSEQ_SWNODE_TEST_PROVIDER, + }, +}; + +static struct platform_driver pwrseq_swnode_provider_driver_2 = { + .probe = pwrseq_swnode_provider_probe, + .driver = { + .name = PWRSEQ_SWNODE_TEST_PROVIDER_2, + }, +}; + +struct pwrseq_probe_order_pdata { + unsigned int probe_count; + int pwrseq_err; +}; + +static const struct pwrseq_probe_order_pdata pwrseq_probe_order_pdata_template; + +static int pwrseq_probe_order_consumer_probe(struct platform_device *pdev) +{ + struct pwrseq_probe_order_pdata *pdata = dev_get_platdata(&pdev->dev); + struct pwrseq_desc *desc; + + pdata->probe_count++; + + desc = devm_pwrseq_get(&pdev->dev, "test-target"); + pdata->pwrseq_err = PTR_ERR_OR_ZERO(desc); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + return 0; +} + +static struct platform_driver pwrseq_probe_order_consumer_driver = { + .probe = pwrseq_probe_order_consumer_probe, + .driver = { + .name = PWRSEQ_PROBE_ORDER_TEST_CONSUMER, + }, +}; + +static struct platform_driver pwrseq_probe_defer_consumer_driver = { + .probe = pwrseq_probe_order_consumer_probe, + .driver = { + .name = PWRSEQ_PROBE_DEFER_TEST_CONSUMER, + }, +}; + +/* + * Verify that driver core orders the probe of a pwrseq consumer after its + * provider. The consumer references the provider through a software node and + * is registered first and we rely on devlink for ordering. + */ +static void pwrseq_swnode_probe_order(struct kunit *test) +{ + struct property_entry properties[2] = { }; + struct pwrseq_probe_order_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + struct fwnode_handle *fwnode; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &pwrseq_swnode_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &pwrseq_probe_order_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode = kunit_software_node_register(test, &pwrseq_test_provider_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + properties[0] = PROPERTY_ENTRY_REF("pwrseq-provider", + &pwrseq_test_provider_swnode); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_PROBE_ORDER_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &pwrseq_probe_order_pdata_template, + .size_data = sizeof(pwrseq_probe_order_pdata_template), + .properties = properties, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->probe_count, 0); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_SWNODE_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .swnode = &pwrseq_test_provider_swnode, + .data = &(const struct pwrseq_swnode_provider_pdata){ + .provider_swnode = &pwrseq_test_provider_swnode, + }, + .size_data = sizeof(struct pwrseq_swnode_provider_pdata), + }; + + prvd = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound = device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_EXPECT_EQ(test, pdata->probe_count, 1); + KUNIT_EXPECT_EQ(test, pdata->pwrseq_err, 0); +} + +/* + * Verify that a pwrseq consumer referencing a provider whose software node is + * not registered yet, defers its probe instead of failing. + * + * The provider software node is deliberately left unregistered when the + * consumer is added. fw_devlink cannot resolve the reference, so it creates no + * supplier link and does not order the consumer - the consumer's probe() runs + * and calls pwrseq_get(), which finds no matching provider and returns + * -EPROBE_DEFER. Once the provider software node and device appear, the + * deferred consumer probes again and binds. + */ +static void pwrseq_swnode_probe_defer_on_unregistered(struct kunit *test) +{ + struct property_entry properties[2] = { }; + struct pwrseq_probe_order_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + struct fwnode_handle *fwnode; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &pwrseq_swnode_provider_driver_2); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &pwrseq_probe_defer_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + properties[0] = PROPERTY_ENTRY_REF("pwrseq-provider", + &pwrseq_test_provider_swnode); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_PROBE_DEFER_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &pwrseq_probe_order_pdata_template, + .size_data = sizeof(pwrseq_probe_order_pdata_template), + .properties = properties, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_GT(test, pdata->probe_count, 0); + KUNIT_ASSERT_EQ(test, pdata->pwrseq_err, -EPROBE_DEFER); + + fwnode = kunit_software_node_register(test, &pwrseq_test_provider_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo = (struct platform_device_info){ + .name = PWRSEQ_SWNODE_TEST_PROVIDER_2, + .id = PLATFORM_DEVID_NONE, + .swnode = &pwrseq_test_provider_swnode, + .data = &(const struct pwrseq_swnode_provider_pdata){ + .provider_swnode = &pwrseq_test_provider_swnode, + }, + .size_data = sizeof(struct pwrseq_swnode_provider_pdata), + }; + + prvd = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound = device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_EXPECT_EQ(test, pdata->pwrseq_err, 0); +} + +static int pwrseq_swnode_test_init(struct kunit *test) +{ + /* + * A device link teardown from an earlier test case, or from this + * suite's previous module load, may still be queued on device_link_mq. + * Flush it so software_node_register() below doesn't spuriously see + * the about-to-be-reused node name as still taken. + */ + device_link_wait_removal(); + + return 0; +} + +static struct kunit_case pwrseq_swnode_probe_order_tests[] = { + KUNIT_CASE(pwrseq_swnode_probe_order), + KUNIT_CASE(pwrseq_swnode_probe_defer_on_unregistered), + { } +}; + +static struct kunit_suite pwrseq_swnode_probe_order_test_suite = { + .name = "pwrseq-swnode-probe-order", + .init = pwrseq_swnode_test_init, + .test_cases = pwrseq_swnode_probe_order_tests, +}; + +kunit_test_suites(&pwrseq_test_suite, + &pwrseq_swnode_probe_order_test_suite); + +MODULE_DESCRIPTION("KUnit test cases for the power sequencing subsystem"); +MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 0b72e3a6992b..91916768e922 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -83,6 +83,18 @@ static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq) return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1); } +static bool pwrseq_pci_m2_e_uart_is_controllable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + /* + * The UART enable is driven through the W_DISABLE2# line. When it is not + * wired up on this connector the enable/disable callbacks are no-ops, so + * the host cannot gate the Bluetooth function on its own. + */ + return !!ctx->w_disable2_gpio; +} + static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = { .name = "uart-enable", .deps = pwrseq_pcie_m2_unit_deps, @@ -104,6 +116,18 @@ static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq) return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1); } +static bool pwrseq_pci_m2_e_pcie_is_controllable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + /* + * The PCIe/WiFi enable is driven through the W_DISABLE1# line. When it + * is not wired up on this connector the enable/disable callbacks are + * no-ops, so the host cannot gate the PCIe/WiFi function on its own. + */ + return !!ctx->w_disable1_gpio; +} + static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = { .name = "pcie-enable", .deps = pwrseq_pcie_m2_unit_deps, @@ -132,12 +156,14 @@ static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = { .name = "uart", .unit = &pwrseq_pcie_m2_e_uart_unit_data, .post_enable = pwrseq_pcie_m2_e_pwup_delay, + .is_controllable = pwrseq_pci_m2_e_uart_is_controllable, }; static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = { .name = "pcie", .unit = &pwrseq_pcie_m2_e_pcie_unit_data, .post_enable = pwrseq_pcie_m2_e_pwup_delay, + .is_controllable = pwrseq_pci_m2_e_pcie_is_controllable, }; static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = { @@ -378,6 +404,21 @@ static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, mutex_unlock(&ctx->list_lock); } +static bool pwrseq_pcie_m2_pci_parent_matches(struct pci_dev *pdev, + struct device_node *pci_parent) +{ + struct device *dev = pdev->dev.parent; + + while (dev) { + if (dev->of_node == pci_parent) + return true; + if (!dev_is_pci(dev)) + break; + dev = dev->parent; + } + return false; +} + static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action, void *data) { @@ -392,7 +433,7 @@ static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action */ struct device_node *pci_parent __free(device_node) = of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0); - if (!pci_parent || (pci_parent != pdev->dev.parent->of_node)) + if (!pci_parent || !pwrseq_pcie_m2_pci_parent_matches(pdev, pci_parent)) return NOTIFY_DONE; switch (action) { @@ -466,7 +507,7 @@ static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) /* Create serdev for existing PCI devices if required */ for_each_pci_dev(pdev) { - if (!pdev->dev.parent || pci_parent != pdev->dev.parent->of_node) + if (!pwrseq_pcie_m2_pci_parent_matches(pdev, pci_parent)) continue; if (!pci_match_id(pwrseq_m2_pci_ids, pdev)) diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index d41793e1fcd9..7f88a29b2c33 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -183,6 +183,19 @@ static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq) return 0; } +static bool pwrseq_qcom_wcn_bt_is_controllable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + /* + * The Bluetooth enable is driven through the BT_EN GPIO. When it is not + * wired up (e.g. hardwired to an always-on pull-up), the enable/disable + * callbacks are no-ops, so the host cannot gate the Bluetooth function + * on its own. + */ + return !!ctx->bt_gpio; +} + static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = { .name = "bluetooth-enable", .deps = pwrseq_qcom_wcn_unit_deps, @@ -217,6 +230,19 @@ static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq) return 0; } +static bool pwrseq_qcom_wcn_wlan_is_controllable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + /* + * The WLAN enable is driven through the WLAN_EN GPIO. When it is not + * wired up (e.g. hardwired to an always-on pull-up), the enable/disable + * callbacks are no-ops, so the host cannot gate the WLAN function on + * its own. + */ + return !!ctx->wlan_gpio; +} + static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = { .name = "wlan-enable", .deps = pwrseq_qcom_wcn_unit_deps, @@ -257,12 +283,14 @@ static const struct pwrseq_target_data pwrseq_qcom_wcn_bt_target_data = { .name = "bluetooth", .unit = &pwrseq_qcom_wcn_bt_unit_data, .post_enable = pwrseq_qcom_wcn_pwup_delay, + .is_controllable = pwrseq_qcom_wcn_bt_is_controllable, }; static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = { .name = "wlan", .unit = &pwrseq_qcom_wcn_wlan_unit_data, .post_enable = pwrseq_qcom_wcn_pwup_delay, + .is_controllable = pwrseq_qcom_wcn_wlan_is_controllable, }; /* There are no separate BT and WLAN enablement pins */ @@ -280,12 +308,14 @@ static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = { .name = "bluetooth", .unit = &pwrseq_qcom_wcn6855_bt_unit_data, .post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert, + .is_controllable = pwrseq_qcom_wcn_bt_is_controllable, }; static const struct pwrseq_target_data pwrseq_qcom_wcn6855_wlan_target_data = { .name = "wlan", .unit = &pwrseq_qcom_wcn6855_wlan_unit_data, .post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert, + .is_controllable = pwrseq_qcom_wcn_wlan_is_controllable, }; static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = { diff --git a/drivers/power/sequencing/pwrseq-renesas-pwrrdy.c b/drivers/power/sequencing/pwrseq-renesas-pwrrdy.c new file mode 100644 index 000000000000..743855620501 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-renesas-pwrrdy.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G3L Power Ready driver + * + */ + +#include <linux/auxiliary_bus.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pwrseq/provider.h> +#include <linux/regmap.h> + +#define SYS_PWRRDY_N 0xd70 +#define SYS_PWRRDY_N_USB_MASK BIT(0) +#define SYS_PWRRDY_N_DSI_MASK BIT(1) +#define SYS_PWRRDY_N_CSI_MASK BIT(2) + +static int pwrseq_rzg3l_set_pwrrdy(struct pwrseq_device *pwrseq, u32 mask, + bool val) +{ + struct regmap *regmap = pwrseq_device_get_drvdata(pwrseq); + + return regmap_assign_bits(regmap, SYS_PWRRDY_N, mask, val); +} + +static int pwrseq_rzg3l_usb_pwrrdy_enable(struct pwrseq_device *pwrseq) +{ + return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_USB_MASK, 0); +} + +static int pwrseq_rzg3l_usb_pwrrdy_disable(struct pwrseq_device *pwrseq) +{ + return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_USB_MASK, 1); +} + +static const struct pwrseq_unit_data pwrseq_rzg3l_usb_pwrrdy_unit = { + .name = "usb-pwrrdy-power-sequence", + .enable = pwrseq_rzg3l_usb_pwrrdy_enable, + .disable = pwrseq_rzg3l_usb_pwrrdy_disable, +}; + +static int pwrseq_rzg3l_dsi_pwrrdy_enable(struct pwrseq_device *pwrseq) +{ + return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_DSI_MASK, 0); +} + +static int pwrseq_rzg3l_dsi_pwrrdy_disable(struct pwrseq_device *pwrseq) +{ + return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_DSI_MASK, 1); +} + +static const struct pwrseq_unit_data pwrseq_rzg3l_dsi_pwrrdy_unit = { + .name = "dsi-pwrrdy-sequence", + .enable = pwrseq_rzg3l_dsi_pwrrdy_enable, + .disable = pwrseq_rzg3l_dsi_pwrrdy_disable, +}; + +static int pwrseq_rzg3l_csi_pwrrdy_enable(struct pwrseq_device *pwrseq) +{ + return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_CSI_MASK, 0); +} + +static int pwrseq_rzg3l_csi_pwrrdy_disable(struct pwrseq_device *pwrseq) +{ + return pwrseq_rzg3l_set_pwrrdy(pwrseq, SYS_PWRRDY_N_CSI_MASK, 1); +} + +static const struct pwrseq_unit_data pwrseq_rzg3l_csi_pwrrdy_unit = { + .name = "csi-pwrrdy-power-sequence", + .enable = pwrseq_rzg3l_csi_pwrrdy_enable, + .disable = pwrseq_rzg3l_csi_pwrrdy_disable, +}; + +static const struct pwrseq_target_data pwrseq_rzg3l_usb_pwrrdy_target = { + .name = "usb-pwrrdy", + .unit = &pwrseq_rzg3l_usb_pwrrdy_unit, +}; + +static const struct pwrseq_target_data pwrseq_rzg3l_dsi_pwrrdy_target = { + .name = "dsi-pwrrdy", + .unit = &pwrseq_rzg3l_dsi_pwrrdy_unit, +}; + +static const struct pwrseq_target_data pwrseq_rzg3l_csi_pwrrdy_target = { + .name = "csi-pwrrdy", + .unit = &pwrseq_rzg3l_csi_pwrrdy_unit, +}; + +static const struct pwrseq_target_data *pwrseq_rzg3l_pwrrdy_targets[] = { + &pwrseq_rzg3l_usb_pwrrdy_target, + &pwrseq_rzg3l_dsi_pwrrdy_target, + &pwrseq_rzg3l_csi_pwrrdy_target, + NULL +}; + +static const char * const pwrseq_rzg3l_pwrrdy_compats[] = { + "renesas,r9a08g046-usbphy-ctrl", + "renesas,r9a08g046-mipi-dsi", + NULL +}; + +static int pwrseq_rzg3l_pwrrdy_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + if (of_device_compatible_match(dev->of_node, pwrseq_rzg3l_pwrrdy_compats)) + return PWRSEQ_MATCH_OK; + + return PWRSEQ_NO_MATCH; +} + +static int pwrseq_rzg3l_pwrrdy_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct pwrseq_config config = { + .parent = &adev->dev, + .owner = THIS_MODULE, + .drvdata = adev->dev.platform_data, + .match = pwrseq_rzg3l_pwrrdy_match, + .targets = pwrseq_rzg3l_pwrrdy_targets, + }; + + return PTR_ERR_OR_ZERO(devm_pwrseq_device_register(&adev->dev, &config)); +} + +static const struct auxiliary_device_id pwrseq_rzg3l_pwrrdy_id_table[] = { + { .name = "rz_sysc.pwrseq-pwrrdy" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(auxiliary, pwrseq_rzg3l_pwrrdy_id_table); + +static struct auxiliary_driver pwrseq_rzg3l_pwrrdy_driver = { + .driver = { + .name = "pwrseq-rzg3l-pwrrdy", + }, + .probe = pwrseq_rzg3l_pwrrdy_probe, + .id_table = pwrseq_rzg3l_pwrrdy_id_table, +}; +module_auxiliary_driver(pwrseq_rzg3l_pwrrdy_driver); + +MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/G3L Power Ready Driver"); +MODULE_LICENSE("GPL"); |
