File Index
Files and directories
Sign in
FILE

pwrseq-thead-gpu.c

File preview with syntax highlighting.

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