Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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 */
24static struct platform_device *drm_dev;
25static struct rocket_device *rdev;
26
27static void
28rocket_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
37static struct rocket_iommu_domain*
38rocket_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
57struct rocket_iommu_domain *
58rocket_iommu_domain_get(struct rocket_file_priv *rocket_priv)
59{
60 kref_get(&rocket_priv->domain->kref);
61 return rocket_priv->domain;
62}
63
64void
65rocket_iommu_domain_put(struct rocket_iommu_domain *domain)
66{
67 kref_put(&domain->kref, rocket_iommu_domain_destroy);
68}
69
70static int
71rocket_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
107err_mm_takedown:
108 mutex_destroy(&rocket_priv->mm_lock);
109 drm_mm_takedown(&rocket_priv->mm);
110 rocket_iommu_domain_put(rocket_priv->domain);
111err_free:
112 kfree(rocket_priv);
113err_put_mod:
114 module_put(THIS_MODULE);
115 return ret;
116}
117
118static void
119rocket_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
131static 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
141DEFINE_DRM_ACCEL_FOPS(rocket_accel_driver_fops);
142
143/*
144 * Rocket driver version:
145 * - 1.0 - initial interface
146 */
147static 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
159static 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
183static 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
202static const struct of_device_id dt_match[] = {
203 { .compatible = "rockchip,rk3588-rknn-core" },
204 {}
205};
206MODULE_DEVICE_TABLE(of, dt_match);
207
208static 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
220static 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
238static 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
254EXPORT_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
259static 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
269static 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
278static void __exit rocket_unregister(void)
279{
280 platform_driver_unregister(&rocket_driver);
281
282 platform_device_unregister(drm_dev);
283}
284
285module_init(rocket_register);
286module_exit(rocket_unregister);
287
288MODULE_LICENSE("GPL");
289MODULE_DESCRIPTION("DRM driver for the Rockchip NPU IP");
290MODULE_AUTHOR("Tomeu Vizoso");