From d4c2d9b5b7ceed14a3a835fd969bb0699b9608d3 Mon Sep 17 00:00:00 2001 From: Michal Wilczynski Date: Mon, 23 Jun 2025 13:42:39 +0200 Subject: power: sequencing: Add T-HEAD TH1520 GPU power sequencer driver 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 Signed-off-by: Michal Wilczynski 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 --- MAINTAINERS | 1 + drivers/power/sequencing/Kconfig | 8 + drivers/power/sequencing/Makefile | 1 + drivers/power/sequencing/pwrseq-thead-gpu.c | 247 ++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 drivers/power/sequencing/pwrseq-thead-gpu.c diff --git a/MAINTAINERS b/MAINTAINERS index a92290fffa16..a7a5f95fb8a4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index ddcc42a98492..0f118d57c1ce 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -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 diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile index 2eec2df7912d..96c1cf0a98ac 100644 --- a/drivers/power/sequencing/Makefile +++ b/drivers/power/sequencing/Makefile @@ -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 index 000000000000..3dd27c32020a --- /dev/null +++ b/drivers/power/sequencing/pwrseq-thead-gpu.c @@ -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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +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 "); +MODULE_DESCRIPTION("T-HEAD TH1520 GPU power sequencer driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 5bcfc4ef40dabcd16a0b736fea7f0d00a9efdbfb Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 24 Jun 2025 16:32:18 +0200 Subject: power: sequencing: thead-gpu: add missing header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using kcalloc(), kfree() etc., we need to include linux/slab.h. While on some architectures it may work fine because the header is pulled in implicitly, on others it triggers the following errors: drivers/power/sequencing/pwrseq-thead-gpu.c: In function ‘pwrseq_thead_gpu_match’: drivers/power/sequencing/pwrseq-thead-gpu.c:147:21: error: implicit declaration of function ‘kcalloc’ [-Wimplicit-function-declaration] 147 | ctx->clks = kcalloc(ctx->num_clks, sizeof(*ctx->clks), GFP_KERNEL); Fixes: d4c2d9b5b7ce ("power: sequencing: Add T-HEAD TH1520 GPU power sequencer driver") Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20250624-pwrseq-match-defines-v1-1-a59d90a951f1@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-thead-gpu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/sequencing/pwrseq-thead-gpu.c b/drivers/power/sequencing/pwrseq-thead-gpu.c index 3dd27c32020a..855c6cc4f3b5 100644 --- a/drivers/power/sequencing/pwrseq-thead-gpu.c +++ b/drivers/power/sequencing/pwrseq-thead-gpu.c @@ -21,6 +21,7 @@ #include #include #include +#include #include -- cgit v1.2.3 From 1a7312b93ab023f68b48a1550049a4f850c2c808 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 24 Jun 2025 16:32:19 +0200 Subject: power: sequencing: extend build coverage with COMPILE_TEST=y Enable building the pwrseq drivers with COMPILE_TEST enabled. This makes it easier to build-test them. Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20250624-pwrseq-match-defines-v1-2-a59d90a951f1@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 0f118d57c1ce..280f92beb5d0 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -16,7 +16,7 @@ if POWER_SEQUENCING config POWER_SEQUENCING_QCOM_WCN tristate "Qualcomm WCN family PMU driver" default m if ARCH_QCOM - depends on OF + depends on OF || COMPILE_TEST help Say Y here to enable the power sequencing driver for Qualcomm WCN Bluetooth/WLAN chipsets. @@ -29,7 +29,7 @@ config POWER_SEQUENCING_QCOM_WCN config POWER_SEQUENCING_TH1520_GPU tristate "T-HEAD TH1520 GPU power sequencing driver" - depends on ARCH_THEAD && AUXILIARY_BUS + depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST help Say Y here to enable the power sequencing driver for the TH1520 SoC GPU. This driver handles the complex clock and reset sequence -- cgit v1.2.3 From 62b5848f73dd4f8ae17304dae54562d0c9ecdd3d Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 24 Jun 2025 16:32:20 +0200 Subject: power: sequencing: add defines for return values of the match() callback Instead of using 0 and 1 as magic numbers, let's add proper defines whose names tell the reader what the meaning behind them is. Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20250624-pwrseq-match-defines-v1-3-a59d90a951f1@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 6 +++--- include/linux/pwrseq/provider.h | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 0ffc259c6bb6..190564e55988 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -628,7 +628,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data) return 0; ret = pwrseq->match(pwrseq, match_data->dev); - if (ret <= 0) + if (ret == PWRSEQ_NO_MATCH || ret < 0) return ret; /* We got the matching device, let's find the right target. */ @@ -651,7 +651,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data) match_data->desc->pwrseq = pwrseq_device_get(pwrseq); - return 1; + return PWRSEQ_MATCH_OK; } /** @@ -684,7 +684,7 @@ struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target) pwrseq_match_device); if (ret < 0) return ERR_PTR(ret); - if (ret == 0) + if (ret == PWRSEQ_NO_MATCH) /* No device matched. */ return ERR_PTR(-EPROBE_DEFER); diff --git a/include/linux/pwrseq/provider.h b/include/linux/pwrseq/provider.h index cbc3607cbfcf..33b3d2c2e39d 100644 --- a/include/linux/pwrseq/provider.h +++ b/include/linux/pwrseq/provider.h @@ -13,6 +13,9 @@ struct pwrseq_device; typedef int (*pwrseq_power_state_func)(struct pwrseq_device *); typedef int (*pwrseq_match_func)(struct pwrseq_device *, struct device *); +#define PWRSEQ_NO_MATCH 0 +#define PWRSEQ_MATCH_OK 1 + /** * struct pwrseq_unit_data - Configuration of a single power sequencing * unit. -- cgit v1.2.3 From f698155029efc708349126c8944fa8c95b28098c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 24 Jun 2025 16:32:21 +0200 Subject: power: sequencing: qcom-wcn: use new defines for match() return values Replace the magic numbers with proper defines we now have in the header. Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20250624-pwrseq-match-defines-v1-4-a59d90a951f1@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-qcom-wcn.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index e8f5030f2639..f14801b4c28e 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -341,12 +341,12 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, * device. */ if (!of_property_present(dev_node, "vddaon-supply")) - return 0; + return PWRSEQ_NO_MATCH; struct device_node *reg_node __free(device_node) = of_parse_phandle(dev_node, "vddaon-supply", 0); if (!reg_node) - return 0; + return PWRSEQ_NO_MATCH; /* * `reg_node` is the PMU AON regulator, its parent is the `regulators` @@ -355,9 +355,9 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, */ if (!reg_node->parent || !reg_node->parent->parent || reg_node->parent->parent != ctx->of_node) - return 0; + return PWRSEQ_NO_MATCH; - return 1; + return PWRSEQ_MATCH_OK; } static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) -- cgit v1.2.3 From 385b735c90ae44dbde65fab76e356a96ff8f67be Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 24 Jun 2025 16:32:22 +0200 Subject: power: sequencing: thead-gpu: use new defines for match() return values Replace the magic numbers with proper defines we now have in the header. Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20250624-pwrseq-match-defines-v1-5-a59d90a951f1@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-thead-gpu.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-thead-gpu.c b/drivers/power/sequencing/pwrseq-thead-gpu.c index 855c6cc4f3b5..7c82a10ca9f6 100644 --- a/drivers/power/sequencing/pwrseq-thead-gpu.c +++ b/drivers/power/sequencing/pwrseq-thead-gpu.c @@ -124,24 +124,25 @@ static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq, /* We only match the specific T-HEAD TH1520 GPU compatible */ if (!of_device_is_compatible(dev->of_node, "thead,th1520-gpu")) - return 0; + return PWRSEQ_NO_MATCH; ret = of_parse_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells", 0, &pwr_spec); if (ret) - return 0; + return PWRSEQ_NO_MATCH; /* 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; + return PWRSEQ_NO_MATCH; } 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; + return ctx->consumer_node == dev->of_node ? + PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; ctx->num_clks = ARRAY_SIZE(clk_names); ctx->clks = kcalloc(ctx->num_clks, sizeof(*ctx->clks), GFP_KERNEL); @@ -163,7 +164,7 @@ static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq, ctx->consumer_node = of_node_get(dev->of_node); - return 1; + return PWRSEQ_MATCH_OK; err_put_clks: clk_bulk_put(ctx->num_clks, ctx->clks); -- cgit v1.2.3 From 07d59dec6795428983a840de85aa02febaf7e01b Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 25 Jun 2025 17:55:43 +0200 Subject: power: sequencing: qcom-wcn: fix bluetooth-wifi copypasta for WCN6855 Prevent a name conflict (which is surprisingly not caught by the framework). Fixes: bd4c8bafcf50 ("power: sequencing: qcom-wcn: improve support for wcn6855") Signed-off-by: Konrad Dybcio Link: https://lore.kernel.org/r/20250625-topic-wcn6855_pwrseq-v1-1-cfb96d599ff8@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-qcom-wcn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index f14801b4c28e..663d9a537065 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -155,7 +155,7 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = { }; static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = { - .name = "wlan-enable", + .name = "bluetooth-enable", .deps = pwrseq_qcom_wcn6855_unit_deps, .enable = pwrseq_qcom_wcn_bt_enable, .disable = pwrseq_qcom_wcn_bt_disable, -- cgit v1.2.3