projects/linux·blob master · refs

307 lines · 6.9 KiB

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