]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
power: sequencing: Add T-HEAD TH1520 GPU power sequencer driver
authorMichal Wilczynski <m.wilczynski@samsung.com>
Mon, 23 Jun 2025 11:42:39 +0000 (13:42 +0200)
committerBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Tue, 24 Jun 2025 13:55:05 +0000 (15:55 +0200)
Introduce the pwrseq-thead-gpu driver, a power sequencer provider for
the Imagination BXM-4-64 GPU on the T-HEAD TH1520 SoC. This driver
controls an auxiliary device instantiated by the AON power domain.

The TH1520 GPU requires a specific sequence to correctly initialize and
power down its resources:
 - Enable GPU clocks (core and sys).
 - De-assert the GPU clock generator reset (clkgen_reset).
 - Introduce a short hardware-required delay.
 - De-assert the GPU core reset. The power-down sequence performs these
   steps in reverse.

Implement this sequence via the pwrseq_power_on and pwrseq_power_off
callbacks.

Crucially, the driver's match function is called when a consumer (the
Imagination GPU driver) requests the "gpu-power" target. During this
match, the sequencer uses clk_bulk_get() and
reset_control_get_exclusive() on the consumer's device to obtain handles
to the GPU's "core" and "sys" clocks, and the GPU core reset.  These,
along with clkgen_reset obtained from parent aon node, allow it to
perform the complete sequence.

Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
Link: https://lore.kernel.org/r/20250623-apr_14_for_sending-v6-1-6583ce0f6c25@samsung.com
[Bartosz: use a ternary operator instead of implicitly casting the
result of a boolean expression to int]
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
MAINTAINERS
drivers/power/sequencing/Kconfig
drivers/power/sequencing/Makefile
drivers/power/sequencing/pwrseq-thead-gpu.c [new file with mode: 0644]

index a92290fffa163f9fe8fe3f04bf66426f9a894409..a7a5f95fb8a484dd16925e4f0a46e97b7b935587 100644 (file)
@@ -21393,6 +21393,7 @@ F:      drivers/mailbox/mailbox-th1520.c
 F:     drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
 F:     drivers/pinctrl/pinctrl-th1520.c
 F:     drivers/pmdomain/thead/
+F:     drivers/power/sequencing/pwrseq-thead-gpu.c
 F:     drivers/reset/reset-th1520.c
 F:     include/dt-bindings/clock/thead,th1520-clk-ap.h
 F:     include/dt-bindings/power/thead,th1520-power.h
index ddcc42a984921c55667c46ac586d259625e1f1a7..0f118d57c1ceddc03954c006f99b5990acf546d4 100644 (file)
@@ -27,4 +27,12 @@ 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_TH1520_GPU
+       tristate "T-HEAD TH1520 GPU power sequencing driver"
+       depends on ARCH_THEAD && AUXILIARY_BUS
+       help
+         Say Y here to enable the power sequencing driver for the TH1520 SoC
+         GPU. This driver handles the complex clock and reset sequence
+         required to power on the Imagination BXM GPU on this platform.
+
 endif
index 2eec2df7912d11827f9ba914177dd2c882e44bce..96c1cf0a98ac54c9c1d65a4bb4e34289a3550fa1 100644 (file)
@@ -4,3 +4,4 @@ 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_TH1520_GPU) += pwrseq-thead-gpu.o
diff --git a/drivers/power/sequencing/pwrseq-thead-gpu.c b/drivers/power/sequencing/pwrseq-thead-gpu.c
new file mode 100644 (file)
index 0000000..3dd27c3
--- /dev/null
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-HEAD TH1520 GPU Power Sequencer Driver
+ *
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Michal Wilczynski <m.wilczynski@samsung.com>
+ *
+ * This driver implements the power sequence for the Imagination BXM-4-64
+ * GPU on the T-HEAD TH1520 SoC. The sequence requires coordinating resources
+ * from both the sequencer's parent device node (clkgen_reset) and the GPU's
+ * device node (clocks and core reset).
+ *
+ * The `match` function is used to acquire the GPU's resources when the
+ * GPU driver requests the "gpu-power" sequence target.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/reset.h>
+
+#include <dt-bindings/power/thead,th1520-power.h>
+
+struct pwrseq_thead_gpu_ctx {
+       struct pwrseq_device *pwrseq;
+       struct reset_control *clkgen_reset;
+       struct device_node *aon_node;
+
+       /* Consumer resources */
+       struct device_node *consumer_node;
+       struct clk_bulk_data *clks;
+       int num_clks;
+       struct reset_control *gpu_reset;
+};
+
+static int pwrseq_thead_gpu_enable(struct pwrseq_device *pwrseq)
+{
+       struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+       int ret;
+
+       if (!ctx->clks || !ctx->gpu_reset)
+               return -ENODEV;
+
+       ret = clk_bulk_prepare_enable(ctx->num_clks, ctx->clks);
+       if (ret)
+               return ret;
+
+       ret = reset_control_deassert(ctx->clkgen_reset);
+       if (ret)
+               goto err_disable_clks;
+
+       /*
+        * According to the hardware manual, a delay of at least 32 clock
+        * cycles is required between de-asserting the clkgen reset and
+        * de-asserting the GPU reset. Assuming a worst-case scenario with
+        * a very high GPU clock frequency, a delay of 1 microsecond is
+        * sufficient to ensure this requirement is met across all
+        * feasible GPU clock speeds.
+        */
+       udelay(1);
+
+       ret = reset_control_deassert(ctx->gpu_reset);
+       if (ret)
+               goto err_assert_clkgen;
+
+       return 0;
+
+err_assert_clkgen:
+       reset_control_assert(ctx->clkgen_reset);
+err_disable_clks:
+       clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
+       return ret;
+}
+
+static int pwrseq_thead_gpu_disable(struct pwrseq_device *pwrseq)
+{
+       struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+       int ret = 0, err;
+
+       if (!ctx->clks || !ctx->gpu_reset)
+               return -ENODEV;
+
+       err = reset_control_assert(ctx->gpu_reset);
+       if (err)
+               ret = err;
+
+       err = reset_control_assert(ctx->clkgen_reset);
+       if (err && !ret)
+               ret = err;
+
+       clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
+
+       /* ret stores values of the first error code */
+       return ret;
+}
+
+static const struct pwrseq_unit_data pwrseq_thead_gpu_unit = {
+       .name = "gpu-power-sequence",
+       .enable = pwrseq_thead_gpu_enable,
+       .disable = pwrseq_thead_gpu_disable,
+};
+
+static const struct pwrseq_target_data pwrseq_thead_gpu_target = {
+       .name = "gpu-power",
+       .unit = &pwrseq_thead_gpu_unit,
+};
+
+static const struct pwrseq_target_data *pwrseq_thead_gpu_targets[] = {
+       &pwrseq_thead_gpu_target,
+       NULL
+};
+
+static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq,
+                                 struct device *dev)
+{
+       struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+       static const char *const clk_names[] = { "core", "sys" };
+       struct of_phandle_args pwr_spec;
+       int i, ret;
+
+       /* We only match the specific T-HEAD TH1520 GPU compatible */
+       if (!of_device_is_compatible(dev->of_node, "thead,th1520-gpu"))
+               return 0;
+
+       ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
+                                        "#power-domain-cells", 0, &pwr_spec);
+       if (ret)
+               return 0;
+
+       /* Additionally verify consumer device has AON as power-domain */
+       if (pwr_spec.np != ctx->aon_node || pwr_spec.args[0] != TH1520_GPU_PD) {
+               of_node_put(pwr_spec.np);
+               return 0;
+       }
+
+       of_node_put(pwr_spec.np);
+
+       /* If a consumer is already bound, only allow a re-match from it */
+       if (ctx->consumer_node)
+               return ctx->consumer_node == dev->of_node ? 1 : 0;
+
+       ctx->num_clks = ARRAY_SIZE(clk_names);
+       ctx->clks = kcalloc(ctx->num_clks, sizeof(*ctx->clks), GFP_KERNEL);
+       if (!ctx->clks)
+               return -ENOMEM;
+
+       for (i = 0; i < ctx->num_clks; i++)
+               ctx->clks[i].id = clk_names[i];
+
+       ret = clk_bulk_get(dev, ctx->num_clks, ctx->clks);
+       if (ret)
+               goto err_free_clks;
+
+       ctx->gpu_reset = reset_control_get_shared(dev, NULL);
+       if (IS_ERR(ctx->gpu_reset)) {
+               ret = PTR_ERR(ctx->gpu_reset);
+               goto err_put_clks;
+       }
+
+       ctx->consumer_node = of_node_get(dev->of_node);
+
+       return 1;
+
+err_put_clks:
+       clk_bulk_put(ctx->num_clks, ctx->clks);
+err_free_clks:
+       kfree(ctx->clks);
+       ctx->clks = NULL;
+
+       return ret;
+}
+
+static int pwrseq_thead_gpu_probe(struct auxiliary_device *adev,
+                                 const struct auxiliary_device_id *id)
+{
+       struct device *dev = &adev->dev;
+       struct device *parent_dev = dev->parent;
+       struct pwrseq_thead_gpu_ctx *ctx;
+       struct pwrseq_config config = {};
+
+       ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+
+       ctx->aon_node = parent_dev->of_node;
+
+       ctx->clkgen_reset =
+               devm_reset_control_get_exclusive(parent_dev, "gpu-clkgen");
+       if (IS_ERR(ctx->clkgen_reset))
+               return dev_err_probe(
+                       dev, PTR_ERR(ctx->clkgen_reset),
+                       "Failed to get GPU clkgen reset from parent\n");
+
+       config.parent = dev;
+       config.owner = THIS_MODULE;
+       config.drvdata = ctx;
+       config.match = pwrseq_thead_gpu_match;
+       config.targets = pwrseq_thead_gpu_targets;
+
+       ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
+       if (IS_ERR(ctx->pwrseq))
+               return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
+                                    "Failed to register power sequencer\n");
+
+       auxiliary_set_drvdata(adev, ctx);
+
+       return 0;
+}
+
+static void pwrseq_thead_gpu_remove(struct auxiliary_device *adev)
+{
+       struct pwrseq_thead_gpu_ctx *ctx = auxiliary_get_drvdata(adev);
+
+       if (ctx->gpu_reset)
+               reset_control_put(ctx->gpu_reset);
+
+       if (ctx->clks) {
+               clk_bulk_put(ctx->num_clks, ctx->clks);
+               kfree(ctx->clks);
+       }
+
+       if (ctx->consumer_node)
+               of_node_put(ctx->consumer_node);
+}
+
+static const struct auxiliary_device_id pwrseq_thead_gpu_id_table[] = {
+       { .name = "th1520_pm_domains.pwrseq-gpu" },
+       {},
+};
+MODULE_DEVICE_TABLE(auxiliary, pwrseq_thead_gpu_id_table);
+
+static struct auxiliary_driver pwrseq_thead_gpu_driver = {
+       .driver = {
+               .name = "pwrseq-thead-gpu",
+       },
+       .probe = pwrseq_thead_gpu_probe,
+       .remove = pwrseq_thead_gpu_remove,
+       .id_table = pwrseq_thead_gpu_id_table,
+};
+module_auxiliary_driver(pwrseq_thead_gpu_driver);
+
+MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>");
+MODULE_DESCRIPTION("T-HEAD TH1520 GPU power sequencer driver");
+MODULE_LICENSE("GPL");