Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Michal Wilczynski 998 99.11% 1 33.33%
Bartosz Golaszewski 9 0.89% 2 66.67%
Total 1007 3


// 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 <linux/slab.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 PWRSEQ_NO_MATCH;

	ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
					 "#power-domain-cells", 0, &pwr_spec);
	if (ret)
		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 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 ?
				PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH;

	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 PWRSEQ_MATCH_OK;

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");