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 28 struct 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 40 static 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 72 err_assert_clkgen: 73 reset_control_assert(ctx->clkgen_reset); 74 err_disable_clks: 75 clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks); 76 return ret; 77 } 78 79 static 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 101 static 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 107 static const struct pwrseq_target_data pwrseq_thead_gpu_target = { 108 .name = "gpu-power", 109 .unit = &pwrseq_thead_gpu_unit, 110 }; 111 112 static const struct pwrseq_target_data *pwrseq_thead_gpu_targets[] = { 113 &pwrseq_thead_gpu_target, 114 NULL 115 }; 116 117 static 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 0; 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 0; 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 0; 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 ? 1 : 0; 145 146 ctx->num_clks = ARRAY_SIZE(clk_names); 147 ctx->clks = kcalloc(ctx->num_clks, sizeof(*ctx->clks), GFP_KERNEL); 148 if (!ctx->clks) 149 return -ENOMEM; 150 151 for (i = 0; i < ctx->num_clks; i++) 152 ctx->clks[i].id = clk_names[i]; 153 154 ret = clk_bulk_get(dev, ctx->num_clks, ctx->clks); 155 if (ret) 156 goto err_free_clks; 157 158 ctx->gpu_reset = reset_control_get_shared(dev, NULL); 159 if (IS_ERR(ctx->gpu_reset)) { 160 ret = PTR_ERR(ctx->gpu_reset); 161 goto err_put_clks; 162 } 163 164 ctx->consumer_node = of_node_get(dev->of_node); 165 166 return 1; 167 168 err_put_clks: 169 clk_bulk_put(ctx->num_clks, ctx->clks); 170 err_free_clks: 171 kfree(ctx->clks); 172 ctx->clks = NULL; 173 174 return ret; 175 } 176 177 static int pwrseq_thead_gpu_probe(struct auxiliary_device *adev, 178 const struct auxiliary_device_id *id) 179 { 180 struct device *dev = &adev->dev; 181 struct device *parent_dev = dev->parent; 182 struct pwrseq_thead_gpu_ctx *ctx; 183 struct pwrseq_config config = {}; 184 185 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 186 if (!ctx) 187 return -ENOMEM; 188 189 ctx->aon_node = parent_dev->of_node; 190 191 ctx->clkgen_reset = 192 devm_reset_control_get_exclusive(parent_dev, "gpu-clkgen"); 193 if (IS_ERR(ctx->clkgen_reset)) 194 return dev_err_probe( 195 dev, PTR_ERR(ctx->clkgen_reset), 196 "Failed to get GPU clkgen reset from parent\n"); 197 198 config.parent = dev; 199 config.owner = THIS_MODULE; 200 config.drvdata = ctx; 201 config.match = pwrseq_thead_gpu_match; 202 config.targets = pwrseq_thead_gpu_targets; 203 204 ctx->pwrseq = devm_pwrseq_device_register(dev, &config); 205 if (IS_ERR(ctx->pwrseq)) 206 return dev_err_probe(dev, PTR_ERR(ctx->pwrseq), 207 "Failed to register power sequencer\n"); 208 209 auxiliary_set_drvdata(adev, ctx); 210 211 return 0; 212 } 213 214 static void pwrseq_thead_gpu_remove(struct auxiliary_device *adev) 215 { 216 struct pwrseq_thead_gpu_ctx *ctx = auxiliary_get_drvdata(adev); 217 218 if (ctx->gpu_reset) 219 reset_control_put(ctx->gpu_reset); 220 221 if (ctx->clks) { 222 clk_bulk_put(ctx->num_clks, ctx->clks); 223 kfree(ctx->clks); 224 } 225 226 if (ctx->consumer_node) 227 of_node_put(ctx->consumer_node); 228 } 229 230 static const struct auxiliary_device_id pwrseq_thead_gpu_id_table[] = { 231 { .name = "th1520_pm_domains.pwrseq-gpu" }, 232 {}, 233 }; 234 MODULE_DEVICE_TABLE(auxiliary, pwrseq_thead_gpu_id_table); 235 236 static struct auxiliary_driver pwrseq_thead_gpu_driver = { 237 .driver = { 238 .name = "pwrseq-thead-gpu", 239 }, 240 .probe = pwrseq_thead_gpu_probe, 241 .remove = pwrseq_thead_gpu_remove, 242 .id_table = pwrseq_thead_gpu_id_table, 243 }; 244 module_auxiliary_driver(pwrseq_thead_gpu_driver); 245 246 MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>"); 247 MODULE_DESCRIPTION("T-HEAD TH1520 GPU power sequencer driver"); 248 MODULE_LICENSE("GPL"); 249