xref: /linux/drivers/accel/rocket/rocket_drv.c (revision 07fdad3a93756b872da7b53647715c48d0f4a2d0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
3 
4 #include <drm/drm_accel.h>
5 #include <drm/drm_drv.h>
6 #include <drm/drm_gem.h>
7 #include <drm/drm_ioctl.h>
8 #include <drm/rocket_accel.h>
9 #include <linux/clk.h>
10 #include <linux/err.h>
11 #include <linux/iommu.h>
12 #include <linux/of.h>
13 #include <linux/platform_device.h>
14 #include <linux/pm_runtime.h>
15 
16 #include "rocket_drv.h"
17 #include "rocket_gem.h"
18 #include "rocket_job.h"
19 
20 /*
21  * Facade device, used to expose a single DRM device to userspace, that
22  * schedules jobs to any RKNN cores in the system.
23  */
24 static struct platform_device *drm_dev;
25 static struct rocket_device *rdev;
26 
27 static void
28 rocket_iommu_domain_destroy(struct kref *kref)
29 {
30 	struct rocket_iommu_domain *domain = container_of(kref, struct rocket_iommu_domain, kref);
31 
32 	iommu_domain_free(domain->domain);
33 	domain->domain = NULL;
34 	kfree(domain);
35 }
36 
37 static struct rocket_iommu_domain*
38 rocket_iommu_domain_create(struct device *dev)
39 {
40 	struct rocket_iommu_domain *domain = kmalloc(sizeof(*domain), GFP_KERNEL);
41 	void *err;
42 
43 	if (!domain)
44 		return ERR_PTR(-ENOMEM);
45 
46 	domain->domain = iommu_paging_domain_alloc(dev);
47 	if (IS_ERR(domain->domain)) {
48 		err = ERR_CAST(domain->domain);
49 		kfree(domain);
50 		return err;
51 	}
52 	kref_init(&domain->kref);
53 
54 	return domain;
55 }
56 
57 struct rocket_iommu_domain *
58 rocket_iommu_domain_get(struct rocket_file_priv *rocket_priv)
59 {
60 	kref_get(&rocket_priv->domain->kref);
61 	return rocket_priv->domain;
62 }
63 
64 void
65 rocket_iommu_domain_put(struct rocket_iommu_domain *domain)
66 {
67 	kref_put(&domain->kref, rocket_iommu_domain_destroy);
68 }
69 
70 static int
71 rocket_open(struct drm_device *dev, struct drm_file *file)
72 {
73 	struct rocket_device *rdev = to_rocket_device(dev);
74 	struct rocket_file_priv *rocket_priv;
75 	u64 start, end;
76 	int ret;
77 
78 	if (!try_module_get(THIS_MODULE))
79 		return -EINVAL;
80 
81 	rocket_priv = kzalloc(sizeof(*rocket_priv), GFP_KERNEL);
82 	if (!rocket_priv) {
83 		ret = -ENOMEM;
84 		goto err_put_mod;
85 	}
86 
87 	rocket_priv->rdev = rdev;
88 	rocket_priv->domain = rocket_iommu_domain_create(rdev->cores[0].dev);
89 	if (IS_ERR(rocket_priv->domain)) {
90 		ret = PTR_ERR(rocket_priv->domain);
91 		goto err_free;
92 	}
93 
94 	file->driver_priv = rocket_priv;
95 
96 	start = rocket_priv->domain->domain->geometry.aperture_start;
97 	end = rocket_priv->domain->domain->geometry.aperture_end;
98 	drm_mm_init(&rocket_priv->mm, start, end - start + 1);
99 	mutex_init(&rocket_priv->mm_lock);
100 
101 	ret = rocket_job_open(rocket_priv);
102 	if (ret)
103 		goto err_mm_takedown;
104 
105 	return 0;
106 
107 err_mm_takedown:
108 	mutex_destroy(&rocket_priv->mm_lock);
109 	drm_mm_takedown(&rocket_priv->mm);
110 	rocket_iommu_domain_put(rocket_priv->domain);
111 err_free:
112 	kfree(rocket_priv);
113 err_put_mod:
114 	module_put(THIS_MODULE);
115 	return ret;
116 }
117 
118 static void
119 rocket_postclose(struct drm_device *dev, struct drm_file *file)
120 {
121 	struct rocket_file_priv *rocket_priv = file->driver_priv;
122 
123 	rocket_job_close(rocket_priv);
124 	mutex_destroy(&rocket_priv->mm_lock);
125 	drm_mm_takedown(&rocket_priv->mm);
126 	rocket_iommu_domain_put(rocket_priv->domain);
127 	kfree(rocket_priv);
128 	module_put(THIS_MODULE);
129 }
130 
131 static const struct drm_ioctl_desc rocket_drm_driver_ioctls[] = {
132 #define ROCKET_IOCTL(n, func) \
133 	DRM_IOCTL_DEF_DRV(ROCKET_##n, rocket_ioctl_##func, 0)
134 
135 	ROCKET_IOCTL(CREATE_BO, create_bo),
136 	ROCKET_IOCTL(SUBMIT, submit),
137 	ROCKET_IOCTL(PREP_BO, prep_bo),
138 	ROCKET_IOCTL(FINI_BO, fini_bo),
139 };
140 
141 DEFINE_DRM_ACCEL_FOPS(rocket_accel_driver_fops);
142 
143 /*
144  * Rocket driver version:
145  * - 1.0 - initial interface
146  */
147 static const struct drm_driver rocket_drm_driver = {
148 	.driver_features	= DRIVER_COMPUTE_ACCEL | DRIVER_GEM,
149 	.open			= rocket_open,
150 	.postclose		= rocket_postclose,
151 	.gem_create_object	= rocket_gem_create_object,
152 	.ioctls			= rocket_drm_driver_ioctls,
153 	.num_ioctls		= ARRAY_SIZE(rocket_drm_driver_ioctls),
154 	.fops			= &rocket_accel_driver_fops,
155 	.name			= "rocket",
156 	.desc			= "rocket DRM",
157 };
158 
159 static int rocket_probe(struct platform_device *pdev)
160 {
161 	if (rdev == NULL) {
162 		/* First core probing, initialize DRM device. */
163 		rdev = rocket_device_init(drm_dev, &rocket_drm_driver);
164 		if (IS_ERR(rdev)) {
165 			dev_err(&pdev->dev, "failed to initialize rocket device\n");
166 			return PTR_ERR(rdev);
167 		}
168 	}
169 
170 	unsigned int core = rdev->num_cores;
171 
172 	dev_set_drvdata(&pdev->dev, rdev);
173 
174 	rdev->cores[core].rdev = rdev;
175 	rdev->cores[core].dev = &pdev->dev;
176 	rdev->cores[core].index = core;
177 
178 	rdev->num_cores++;
179 
180 	return rocket_core_init(&rdev->cores[core]);
181 }
182 
183 static void rocket_remove(struct platform_device *pdev)
184 {
185 	struct device *dev = &pdev->dev;
186 
187 	for (unsigned int core = 0; core < rdev->num_cores; core++) {
188 		if (rdev->cores[core].dev == dev) {
189 			rocket_core_fini(&rdev->cores[core]);
190 			rdev->num_cores--;
191 			break;
192 		}
193 	}
194 
195 	if (rdev->num_cores == 0) {
196 		/* Last core removed, deinitialize DRM device. */
197 		rocket_device_fini(rdev);
198 		rdev = NULL;
199 	}
200 }
201 
202 static const struct of_device_id dt_match[] = {
203 	{ .compatible = "rockchip,rk3588-rknn-core" },
204 	{}
205 };
206 MODULE_DEVICE_TABLE(of, dt_match);
207 
208 static int find_core_for_dev(struct device *dev)
209 {
210 	struct rocket_device *rdev = dev_get_drvdata(dev);
211 
212 	for (unsigned int core = 0; core < rdev->num_cores; core++) {
213 		if (dev == rdev->cores[core].dev)
214 			return core;
215 	}
216 
217 	return -1;
218 }
219 
220 static int rocket_device_runtime_resume(struct device *dev)
221 {
222 	struct rocket_device *rdev = dev_get_drvdata(dev);
223 	int core = find_core_for_dev(dev);
224 	int err = 0;
225 
226 	if (core < 0)
227 		return -ENODEV;
228 
229 	err = clk_bulk_prepare_enable(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
230 	if (err) {
231 		dev_err(dev, "failed to enable (%d) clocks for core %d\n", err, core);
232 		return err;
233 	}
234 
235 	return 0;
236 }
237 
238 static int rocket_device_runtime_suspend(struct device *dev)
239 {
240 	struct rocket_device *rdev = dev_get_drvdata(dev);
241 	int core = find_core_for_dev(dev);
242 
243 	if (core < 0)
244 		return -ENODEV;
245 
246 	if (!rocket_job_is_idle(&rdev->cores[core]))
247 		return -EBUSY;
248 
249 	clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
250 
251 	return 0;
252 }
253 
254 EXPORT_GPL_DEV_PM_OPS(rocket_pm_ops) = {
255 	RUNTIME_PM_OPS(rocket_device_runtime_suspend, rocket_device_runtime_resume, NULL)
256 	SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
257 };
258 
259 static struct platform_driver rocket_driver = {
260 	.probe = rocket_probe,
261 	.remove = rocket_remove,
262 	.driver	 = {
263 		.name = "rocket",
264 		.pm = pm_ptr(&rocket_pm_ops),
265 		.of_match_table = dt_match,
266 	},
267 };
268 
269 static int __init rocket_register(void)
270 {
271 	drm_dev = platform_device_register_simple("rknn", -1, NULL, 0);
272 	if (IS_ERR(drm_dev))
273 		return PTR_ERR(drm_dev);
274 
275 	return platform_driver_register(&rocket_driver);
276 }
277 
278 static void __exit rocket_unregister(void)
279 {
280 	platform_driver_unregister(&rocket_driver);
281 
282 	platform_device_unregister(drm_dev);
283 }
284 
285 module_init(rocket_register);
286 module_exit(rocket_unregister);
287 
288 MODULE_LICENSE("GPL");
289 MODULE_DESCRIPTION("DRM driver for the Rockchip NPU IP");
290 MODULE_AUTHOR("Tomeu Vizoso");
291