Skip to content

Commit d4c2d9b

Browse files
Michal WilczynskiBartosz Golaszewski
authored andcommitted
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 <[email protected]> Signed-off-by: Michal Wilczynski <[email protected]> Link: https://lore.kernel.org/r/[email protected] [Bartosz: use a ternary operator instead of implicitly casting the result of a boolean expression to int] Signed-off-by: Bartosz Golaszewski <[email protected]>
1 parent 19272b3 commit d4c2d9b

File tree

4 files changed

+257
-0
lines changed

4 files changed

+257
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21393,6 +21393,7 @@ F: drivers/mailbox/mailbox-th1520.c
2139321393
F: drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
2139421394
F: drivers/pinctrl/pinctrl-th1520.c
2139521395
F: drivers/pmdomain/thead/
21396+
F: drivers/power/sequencing/pwrseq-thead-gpu.c
2139621397
F: drivers/reset/reset-th1520.c
2139721398
F: include/dt-bindings/clock/thead,th1520-clk-ap.h
2139821399
F: include/dt-bindings/power/thead,th1520-power.h

drivers/power/sequencing/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,12 @@ config POWER_SEQUENCING_QCOM_WCN
2727
this driver is needed for correct power control or else we'd risk not
2828
respecting the required delays between enabling Bluetooth and WLAN.
2929

30+
config POWER_SEQUENCING_TH1520_GPU
31+
tristate "T-HEAD TH1520 GPU power sequencing driver"
32+
depends on ARCH_THEAD && AUXILIARY_BUS
33+
help
34+
Say Y here to enable the power sequencing driver for the TH1520 SoC
35+
GPU. This driver handles the complex clock and reset sequence
36+
required to power on the Imagination BXM GPU on this platform.
37+
3038
endif

drivers/power/sequencing/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o
44
pwrseq-core-y := core.o
55

66
obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o
7+
obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* T-HEAD TH1520 GPU Power Sequencer Driver
4+
*
5+
* Copyright (c) 2025 Samsung Electronics Co., Ltd.
6+
* Author: Michal Wilczynski <[email protected]>
7+
*
8+
* This driver implements the power sequence for the Imagination BXM-4-64
9+
* GPU on the T-HEAD TH1520 SoC. The sequence requires coordinating resources
10+
* from both the sequencer's parent device node (clkgen_reset) and the GPU's
11+
* device node (clocks and core reset).
12+
*
13+
* The `match` function is used to acquire the GPU's resources when the
14+
* GPU driver requests the "gpu-power" sequence target.
15+
*/
16+
17+
#include <linux/auxiliary_bus.h>
18+
#include <linux/clk.h>
19+
#include <linux/delay.h>
20+
#include <linux/module.h>
21+
#include <linux/of.h>
22+
#include <linux/pwrseq/provider.h>
23+
#include <linux/reset.h>
24+
25+
#include <dt-bindings/power/thead,th1520-power.h>
26+
27+
struct pwrseq_thead_gpu_ctx {
28+
struct pwrseq_device *pwrseq;
29+
struct reset_control *clkgen_reset;
30+
struct device_node *aon_node;
31+
32+
/* Consumer resources */
33+
struct device_node *consumer_node;
34+
struct clk_bulk_data *clks;
35+
int num_clks;
36+
struct reset_control *gpu_reset;
37+
};
38+
39+
static int pwrseq_thead_gpu_enable(struct pwrseq_device *pwrseq)
40+
{
41+
struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
42+
int ret;
43+
44+
if (!ctx->clks || !ctx->gpu_reset)
45+
return -ENODEV;
46+
47+
ret = clk_bulk_prepare_enable(ctx->num_clks, ctx->clks);
48+
if (ret)
49+
return ret;
50+
51+
ret = reset_control_deassert(ctx->clkgen_reset);
52+
if (ret)
53+
goto err_disable_clks;
54+
55+
/*
56+
* According to the hardware manual, a delay of at least 32 clock
57+
* cycles is required between de-asserting the clkgen reset and
58+
* de-asserting the GPU reset. Assuming a worst-case scenario with
59+
* a very high GPU clock frequency, a delay of 1 microsecond is
60+
* sufficient to ensure this requirement is met across all
61+
* feasible GPU clock speeds.
62+
*/
63+
udelay(1);
64+
65+
ret = reset_control_deassert(ctx->gpu_reset);
66+
if (ret)
67+
goto err_assert_clkgen;
68+
69+
return 0;
70+
71+
err_assert_clkgen:
72+
reset_control_assert(ctx->clkgen_reset);
73+
err_disable_clks:
74+
clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
75+
return ret;
76+
}
77+
78+
static int pwrseq_thead_gpu_disable(struct pwrseq_device *pwrseq)
79+
{
80+
struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
81+
int ret = 0, err;
82+
83+
if (!ctx->clks || !ctx->gpu_reset)
84+
return -ENODEV;
85+
86+
err = reset_control_assert(ctx->gpu_reset);
87+
if (err)
88+
ret = err;
89+
90+
err = reset_control_assert(ctx->clkgen_reset);
91+
if (err && !ret)
92+
ret = err;
93+
94+
clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
95+
96+
/* ret stores values of the first error code */
97+
return ret;
98+
}
99+
100+
static const struct pwrseq_unit_data pwrseq_thead_gpu_unit = {
101+
.name = "gpu-power-sequence",
102+
.enable = pwrseq_thead_gpu_enable,
103+
.disable = pwrseq_thead_gpu_disable,
104+
};
105+
106+
static const struct pwrseq_target_data pwrseq_thead_gpu_target = {
107+
.name = "gpu-power",
108+
.unit = &pwrseq_thead_gpu_unit,
109+
};
110+
111+
static const struct pwrseq_target_data *pwrseq_thead_gpu_targets[] = {
112+
&pwrseq_thead_gpu_target,
113+
NULL
114+
};
115+
116+
static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq,
117+
struct device *dev)
118+
{
119+
struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
120+
static const char *const clk_names[] = { "core", "sys" };
121+
struct of_phandle_args pwr_spec;
122+
int i, ret;
123+
124+
/* We only match the specific T-HEAD TH1520 GPU compatible */
125+
if (!of_device_is_compatible(dev->of_node, "thead,th1520-gpu"))
126+
return 0;
127+
128+
ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
129+
"#power-domain-cells", 0, &pwr_spec);
130+
if (ret)
131+
return 0;
132+
133+
/* Additionally verify consumer device has AON as power-domain */
134+
if (pwr_spec.np != ctx->aon_node || pwr_spec.args[0] != TH1520_GPU_PD) {
135+
of_node_put(pwr_spec.np);
136+
return 0;
137+
}
138+
139+
of_node_put(pwr_spec.np);
140+
141+
/* If a consumer is already bound, only allow a re-match from it */
142+
if (ctx->consumer_node)
143+
return ctx->consumer_node == dev->of_node ? 1 : 0;
144+
145+
ctx->num_clks = ARRAY_SIZE(clk_names);
146+
ctx->clks = kcalloc(ctx->num_clks, sizeof(*ctx->clks), GFP_KERNEL);
147+
if (!ctx->clks)
148+
return -ENOMEM;
149+
150+
for (i = 0; i < ctx->num_clks; i++)
151+
ctx->clks[i].id = clk_names[i];
152+
153+
ret = clk_bulk_get(dev, ctx->num_clks, ctx->clks);
154+
if (ret)
155+
goto err_free_clks;
156+
157+
ctx->gpu_reset = reset_control_get_shared(dev, NULL);
158+
if (IS_ERR(ctx->gpu_reset)) {
159+
ret = PTR_ERR(ctx->gpu_reset);
160+
goto err_put_clks;
161+
}
162+
163+
ctx->consumer_node = of_node_get(dev->of_node);
164+
165+
return 1;
166+
167+
err_put_clks:
168+
clk_bulk_put(ctx->num_clks, ctx->clks);
169+
err_free_clks:
170+
kfree(ctx->clks);
171+
ctx->clks = NULL;
172+
173+
return ret;
174+
}
175+
176+
static int pwrseq_thead_gpu_probe(struct auxiliary_device *adev,
177+
const struct auxiliary_device_id *id)
178+
{
179+
struct device *dev = &adev->dev;
180+
struct device *parent_dev = dev->parent;
181+
struct pwrseq_thead_gpu_ctx *ctx;
182+
struct pwrseq_config config = {};
183+
184+
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
185+
if (!ctx)
186+
return -ENOMEM;
187+
188+
ctx->aon_node = parent_dev->of_node;
189+
190+
ctx->clkgen_reset =
191+
devm_reset_control_get_exclusive(parent_dev, "gpu-clkgen");
192+
if (IS_ERR(ctx->clkgen_reset))
193+
return dev_err_probe(
194+
dev, PTR_ERR(ctx->clkgen_reset),
195+
"Failed to get GPU clkgen reset from parent\n");
196+
197+
config.parent = dev;
198+
config.owner = THIS_MODULE;
199+
config.drvdata = ctx;
200+
config.match = pwrseq_thead_gpu_match;
201+
config.targets = pwrseq_thead_gpu_targets;
202+
203+
ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
204+
if (IS_ERR(ctx->pwrseq))
205+
return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
206+
"Failed to register power sequencer\n");
207+
208+
auxiliary_set_drvdata(adev, ctx);
209+
210+
return 0;
211+
}
212+
213+
static void pwrseq_thead_gpu_remove(struct auxiliary_device *adev)
214+
{
215+
struct pwrseq_thead_gpu_ctx *ctx = auxiliary_get_drvdata(adev);
216+
217+
if (ctx->gpu_reset)
218+
reset_control_put(ctx->gpu_reset);
219+
220+
if (ctx->clks) {
221+
clk_bulk_put(ctx->num_clks, ctx->clks);
222+
kfree(ctx->clks);
223+
}
224+
225+
if (ctx->consumer_node)
226+
of_node_put(ctx->consumer_node);
227+
}
228+
229+
static const struct auxiliary_device_id pwrseq_thead_gpu_id_table[] = {
230+
{ .name = "th1520_pm_domains.pwrseq-gpu" },
231+
{},
232+
};
233+
MODULE_DEVICE_TABLE(auxiliary, pwrseq_thead_gpu_id_table);
234+
235+
static struct auxiliary_driver pwrseq_thead_gpu_driver = {
236+
.driver = {
237+
.name = "pwrseq-thead-gpu",
238+
},
239+
.probe = pwrseq_thead_gpu_probe,
240+
.remove = pwrseq_thead_gpu_remove,
241+
.id_table = pwrseq_thead_gpu_id_table,
242+
};
243+
module_auxiliary_driver(pwrseq_thead_gpu_driver);
244+
245+
MODULE_AUTHOR("Michal Wilczynski <[email protected]>");
246+
MODULE_DESCRIPTION("T-HEAD TH1520 GPU power sequencer driver");
247+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)