pwrseq-thead-gpu.c 6.4 KB

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