@@ -21364,6 +21364,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
@@ -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_THEAD_GPU
+ tristate "T-HEAD TH1520 GPU power sequencing driver"
+ depends on ARCH_THEAD
+ 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
@@ -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_THEAD_GPU) += pwrseq-thead-gpu.o
new file mode 100644
@@ -0,0 +1,183 @@
+// 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 GPU
+ * on the T-HEAD TH1520 SoC. The sequence requires coordinating resources
+ * from both the sequencer's 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/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/reset.h>
+
+struct pwrseq_thead_gpu_ctx {
+ struct pwrseq_device *pwrseq;
+ struct reset_control *clkgen_reset;
+
+ /* Consumer resources */
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct reset_control *gpu_reset;
+};
+
+static int pwrseq_thead_gpu_power_on(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_power_off(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ if (!ctx->clks || !ctx->gpu_reset)
+ return -ENODEV;
+
+ reset_control_assert(ctx->gpu_reset);
+ reset_control_assert(ctx->clkgen_reset);
+ clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
+
+ return 0;
+}
+
+static const struct pwrseq_unit_data pwrseq_thead_gpu_unit = {
+ .name = "gpu-power-sequence",
+ .enable = pwrseq_thead_gpu_power_on,
+ .disable = pwrseq_thead_gpu_power_off,
+};
+
+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" };
+ 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;
+
+ /* Prevent multiple consumers from attaching */
+ if (ctx->gpu_reset || ctx->clks)
+ return -EBUSY;
+
+ ctx->num_clks = ARRAY_SIZE(clk_names);
+ ctx->clks = devm_kcalloc(dev, 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 = devm_clk_bulk_get(dev, ctx->num_clks, ctx->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get GPU clocks\n");
+
+ ctx->gpu_reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(ctx->gpu_reset))
+ return dev_err_probe(dev, PTR_ERR(ctx->gpu_reset), "Failed to get GPU reset\n");
+
+ return 1;
+}
+
+static int pwrseq_thead_gpu_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwrseq_thead_gpu_ctx *ctx;
+ struct pwrseq_config config = {};
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->clkgen_reset = devm_reset_control_get_exclusive(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\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");
+
+ return 0;
+}
+
+static const struct of_device_id pwrseq_thead_gpu_of_match[] = {
+ { .compatible = "thead,th1520-gpu-pwrseq" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pwrseq_thead_gpu_of_match);
+
+static struct platform_driver pwrseq_thead_gpu_driver = {
+ .driver = {
+ .name = "pwrseq-thead-gpu",
+ .of_match_table = pwrseq_thead_gpu_of_match,
+ },
+ .probe = pwrseq_thead_gpu_probe,
+};
+module_platform_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");
Introduce the pwrseq-thead-gpu driver, a power sequencer provider for the Imagination BXM-4-64 GPU on the T-HEAD TH1520 SoC. 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. It binds to the "thead,th1520-gpu-pwrseq" device tree node, from which it acquires the clkgen_reset. 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 devm_clk_bulk_get() and devm_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 its own clkgen_reset, allow it to perform the complete sequence. Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com> --- MAINTAINERS | 1 + drivers/power/sequencing/Kconfig | 8 ++ drivers/power/sequencing/Makefile | 1 + drivers/power/sequencing/pwrseq-thead-gpu.c | 183 ++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+)