1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Copyright (C) 2014-2018 Broadcom */ |
3 | |
4 | /** |
5 | * DOC: Broadcom V3D Graphics Driver |
6 | * |
7 | * This driver supports the Broadcom V3D 3.3 and 4.1 OpenGL ES GPUs. |
8 | * For V3D 2.x support, see the VC4 driver. |
9 | * |
10 | * The V3D GPU includes a tiled render (composed of a bin and render |
11 | * pipelines), the TFU (texture formatting unit), and the CSD (compute |
12 | * shader dispatch). |
13 | */ |
14 | |
15 | #include <linux/clk.h> |
16 | #include <linux/device.h> |
17 | #include <linux/dma-mapping.h> |
18 | #include <linux/io.h> |
19 | #include <linux/module.h> |
20 | #include <linux/of_platform.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/reset.h> |
23 | |
24 | #include <drm/drm_drv.h> |
25 | #include <drm/drm_managed.h> |
26 | #include <uapi/drm/v3d_drm.h> |
27 | |
28 | #include "v3d_drv.h" |
29 | #include "v3d_regs.h" |
30 | |
31 | #define DRIVER_NAME "v3d" |
32 | #define DRIVER_DESC "Broadcom V3D graphics" |
33 | #define DRIVER_DATE "20180419" |
34 | #define DRIVER_MAJOR 1 |
35 | #define DRIVER_MINOR 0 |
36 | #define DRIVER_PATCHLEVEL 0 |
37 | |
38 | static int v3d_get_param_ioctl(struct drm_device *dev, void *data, |
39 | struct drm_file *file_priv) |
40 | { |
41 | struct v3d_dev *v3d = to_v3d_dev(dev); |
42 | struct drm_v3d_get_param *args = data; |
43 | static const u32 reg_map[] = { |
44 | [DRM_V3D_PARAM_V3D_UIFCFG] = V3D_HUB_UIFCFG, |
45 | [DRM_V3D_PARAM_V3D_HUB_IDENT1] = V3D_HUB_IDENT1, |
46 | [DRM_V3D_PARAM_V3D_HUB_IDENT2] = V3D_HUB_IDENT2, |
47 | [DRM_V3D_PARAM_V3D_HUB_IDENT3] = V3D_HUB_IDENT3, |
48 | [DRM_V3D_PARAM_V3D_CORE0_IDENT0] = V3D_CTL_IDENT0, |
49 | [DRM_V3D_PARAM_V3D_CORE0_IDENT1] = V3D_CTL_IDENT1, |
50 | [DRM_V3D_PARAM_V3D_CORE0_IDENT2] = V3D_CTL_IDENT2, |
51 | }; |
52 | |
53 | if (args->pad != 0) |
54 | return -EINVAL; |
55 | |
56 | /* Note that DRM_V3D_PARAM_V3D_CORE0_IDENT0 is 0, so we need |
57 | * to explicitly allow it in the "the register in our |
58 | * parameter map" check. |
59 | */ |
60 | if (args->param < ARRAY_SIZE(reg_map) && |
61 | (reg_map[args->param] || |
62 | args->param == DRM_V3D_PARAM_V3D_CORE0_IDENT0)) { |
63 | u32 offset = reg_map[args->param]; |
64 | |
65 | if (args->value != 0) |
66 | return -EINVAL; |
67 | |
68 | if (args->param >= DRM_V3D_PARAM_V3D_CORE0_IDENT0 && |
69 | args->param <= DRM_V3D_PARAM_V3D_CORE0_IDENT2) { |
70 | args->value = V3D_CORE_READ(0, offset); |
71 | } else { |
72 | args->value = V3D_READ(offset); |
73 | } |
74 | return 0; |
75 | } |
76 | |
77 | switch (args->param) { |
78 | case DRM_V3D_PARAM_SUPPORTS_TFU: |
79 | args->value = 1; |
80 | return 0; |
81 | case DRM_V3D_PARAM_SUPPORTS_CSD: |
82 | args->value = v3d_has_csd(v3d); |
83 | return 0; |
84 | case DRM_V3D_PARAM_SUPPORTS_CACHE_FLUSH: |
85 | args->value = 1; |
86 | return 0; |
87 | case DRM_V3D_PARAM_SUPPORTS_PERFMON: |
88 | args->value = (v3d->ver >= 40); |
89 | return 0; |
90 | case DRM_V3D_PARAM_SUPPORTS_MULTISYNC_EXT: |
91 | args->value = 1; |
92 | return 0; |
93 | default: |
94 | DRM_DEBUG("Unknown parameter %d\n" , args->param); |
95 | return -EINVAL; |
96 | } |
97 | } |
98 | |
99 | static int |
100 | v3d_open(struct drm_device *dev, struct drm_file *file) |
101 | { |
102 | struct v3d_dev *v3d = to_v3d_dev(dev); |
103 | struct v3d_file_priv *v3d_priv; |
104 | struct drm_gpu_scheduler *sched; |
105 | int i; |
106 | |
107 | v3d_priv = kzalloc(size: sizeof(*v3d_priv), GFP_KERNEL); |
108 | if (!v3d_priv) |
109 | return -ENOMEM; |
110 | |
111 | v3d_priv->v3d = v3d; |
112 | |
113 | for (i = 0; i < V3D_MAX_QUEUES; i++) { |
114 | sched = &v3d->queue[i].sched; |
115 | drm_sched_entity_init(entity: &v3d_priv->sched_entity[i], |
116 | priority: DRM_SCHED_PRIORITY_NORMAL, sched_list: &sched, |
117 | num_sched_list: 1, NULL); |
118 | } |
119 | |
120 | v3d_perfmon_open_file(v3d_priv); |
121 | file->driver_priv = v3d_priv; |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static void |
127 | v3d_postclose(struct drm_device *dev, struct drm_file *file) |
128 | { |
129 | struct v3d_file_priv *v3d_priv = file->driver_priv; |
130 | enum v3d_queue q; |
131 | |
132 | for (q = 0; q < V3D_MAX_QUEUES; q++) |
133 | drm_sched_entity_destroy(entity: &v3d_priv->sched_entity[q]); |
134 | |
135 | v3d_perfmon_close_file(v3d_priv); |
136 | kfree(objp: v3d_priv); |
137 | } |
138 | |
139 | DEFINE_DRM_GEM_FOPS(v3d_drm_fops); |
140 | |
141 | /* DRM_AUTH is required on SUBMIT_CL for now, while we don't have GMP |
142 | * protection between clients. Note that render nodes would be |
143 | * able to submit CLs that could access BOs from clients authenticated |
144 | * with the master node. The TFU doesn't use the GMP, so it would |
145 | * need to stay DRM_AUTH until we do buffer size/offset validation. |
146 | */ |
147 | static const struct drm_ioctl_desc v3d_drm_ioctls[] = { |
148 | DRM_IOCTL_DEF_DRV(V3D_SUBMIT_CL, v3d_submit_cl_ioctl, DRM_RENDER_ALLOW | DRM_AUTH), |
149 | DRM_IOCTL_DEF_DRV(V3D_WAIT_BO, v3d_wait_bo_ioctl, DRM_RENDER_ALLOW), |
150 | DRM_IOCTL_DEF_DRV(V3D_CREATE_BO, v3d_create_bo_ioctl, DRM_RENDER_ALLOW), |
151 | DRM_IOCTL_DEF_DRV(V3D_MMAP_BO, v3d_mmap_bo_ioctl, DRM_RENDER_ALLOW), |
152 | DRM_IOCTL_DEF_DRV(V3D_GET_PARAM, v3d_get_param_ioctl, DRM_RENDER_ALLOW), |
153 | DRM_IOCTL_DEF_DRV(V3D_GET_BO_OFFSET, v3d_get_bo_offset_ioctl, DRM_RENDER_ALLOW), |
154 | DRM_IOCTL_DEF_DRV(V3D_SUBMIT_TFU, v3d_submit_tfu_ioctl, DRM_RENDER_ALLOW | DRM_AUTH), |
155 | DRM_IOCTL_DEF_DRV(V3D_SUBMIT_CSD, v3d_submit_csd_ioctl, DRM_RENDER_ALLOW | DRM_AUTH), |
156 | DRM_IOCTL_DEF_DRV(V3D_PERFMON_CREATE, v3d_perfmon_create_ioctl, DRM_RENDER_ALLOW), |
157 | DRM_IOCTL_DEF_DRV(V3D_PERFMON_DESTROY, v3d_perfmon_destroy_ioctl, DRM_RENDER_ALLOW), |
158 | DRM_IOCTL_DEF_DRV(V3D_PERFMON_GET_VALUES, v3d_perfmon_get_values_ioctl, DRM_RENDER_ALLOW), |
159 | }; |
160 | |
161 | static const struct drm_driver v3d_drm_driver = { |
162 | .driver_features = (DRIVER_GEM | |
163 | DRIVER_RENDER | |
164 | DRIVER_SYNCOBJ), |
165 | |
166 | .open = v3d_open, |
167 | .postclose = v3d_postclose, |
168 | |
169 | #if defined(CONFIG_DEBUG_FS) |
170 | .debugfs_init = v3d_debugfs_init, |
171 | #endif |
172 | |
173 | .gem_create_object = v3d_create_object, |
174 | .gem_prime_import_sg_table = v3d_prime_import_sg_table, |
175 | |
176 | .ioctls = v3d_drm_ioctls, |
177 | .num_ioctls = ARRAY_SIZE(v3d_drm_ioctls), |
178 | .fops = &v3d_drm_fops, |
179 | |
180 | .name = DRIVER_NAME, |
181 | .desc = DRIVER_DESC, |
182 | .date = DRIVER_DATE, |
183 | .major = DRIVER_MAJOR, |
184 | .minor = DRIVER_MINOR, |
185 | .patchlevel = DRIVER_PATCHLEVEL, |
186 | }; |
187 | |
188 | static const struct of_device_id v3d_of_match[] = { |
189 | { .compatible = "brcm,2711-v3d" }, |
190 | { .compatible = "brcm,7268-v3d" }, |
191 | { .compatible = "brcm,7278-v3d" }, |
192 | {}, |
193 | }; |
194 | MODULE_DEVICE_TABLE(of, v3d_of_match); |
195 | |
196 | static int |
197 | map_regs(struct v3d_dev *v3d, void __iomem **regs, const char *name) |
198 | { |
199 | *regs = devm_platform_ioremap_resource_byname(v3d_to_pdev(v3d), name); |
200 | return PTR_ERR_OR_ZERO(ptr: *regs); |
201 | } |
202 | |
203 | static int v3d_platform_drm_probe(struct platform_device *pdev) |
204 | { |
205 | struct device *dev = &pdev->dev; |
206 | struct drm_device *drm; |
207 | struct v3d_dev *v3d; |
208 | int ret; |
209 | u32 mmu_debug; |
210 | u32 ident1; |
211 | u64 mask; |
212 | |
213 | v3d = devm_drm_dev_alloc(dev, &v3d_drm_driver, struct v3d_dev, drm); |
214 | if (IS_ERR(ptr: v3d)) |
215 | return PTR_ERR(ptr: v3d); |
216 | |
217 | drm = &v3d->drm; |
218 | |
219 | platform_set_drvdata(pdev, data: drm); |
220 | |
221 | ret = map_regs(v3d, regs: &v3d->hub_regs, name: "hub" ); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | ret = map_regs(v3d, regs: &v3d->core_regs[0], name: "core0" ); |
226 | if (ret) |
227 | return ret; |
228 | |
229 | mmu_debug = V3D_READ(V3D_MMU_DEBUG_INFO); |
230 | mask = DMA_BIT_MASK(30 + V3D_GET_FIELD(mmu_debug, V3D_MMU_PA_WIDTH)); |
231 | ret = dma_set_mask_and_coherent(dev, mask); |
232 | if (ret) |
233 | return ret; |
234 | |
235 | v3d->va_width = 30 + V3D_GET_FIELD(mmu_debug, V3D_MMU_VA_WIDTH); |
236 | |
237 | ident1 = V3D_READ(V3D_HUB_IDENT1); |
238 | v3d->ver = (V3D_GET_FIELD(ident1, V3D_HUB_IDENT1_TVER) * 10 + |
239 | V3D_GET_FIELD(ident1, V3D_HUB_IDENT1_REV)); |
240 | v3d->cores = V3D_GET_FIELD(ident1, V3D_HUB_IDENT1_NCORES); |
241 | WARN_ON(v3d->cores > 1); /* multicore not yet implemented */ |
242 | |
243 | v3d->reset = devm_reset_control_get_exclusive(dev, NULL); |
244 | if (IS_ERR(ptr: v3d->reset)) { |
245 | ret = PTR_ERR(ptr: v3d->reset); |
246 | |
247 | if (ret == -EPROBE_DEFER) |
248 | return ret; |
249 | |
250 | v3d->reset = NULL; |
251 | ret = map_regs(v3d, regs: &v3d->bridge_regs, name: "bridge" ); |
252 | if (ret) { |
253 | dev_err(dev, |
254 | "Failed to get reset control or bridge regs\n" ); |
255 | return ret; |
256 | } |
257 | } |
258 | |
259 | if (v3d->ver < 41) { |
260 | ret = map_regs(v3d, regs: &v3d->gca_regs, name: "gca" ); |
261 | if (ret) |
262 | return ret; |
263 | } |
264 | |
265 | v3d->mmu_scratch = dma_alloc_wc(dev, size: 4096, dma_addr: &v3d->mmu_scratch_paddr, |
266 | GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO); |
267 | if (!v3d->mmu_scratch) { |
268 | dev_err(dev, "Failed to allocate MMU scratch page\n" ); |
269 | return -ENOMEM; |
270 | } |
271 | |
272 | ret = v3d_gem_init(dev: drm); |
273 | if (ret) |
274 | goto dma_free; |
275 | |
276 | ret = v3d_irq_init(v3d); |
277 | if (ret) |
278 | goto gem_destroy; |
279 | |
280 | ret = drm_dev_register(dev: drm, flags: 0); |
281 | if (ret) |
282 | goto irq_disable; |
283 | |
284 | return 0; |
285 | |
286 | irq_disable: |
287 | v3d_irq_disable(v3d); |
288 | gem_destroy: |
289 | v3d_gem_destroy(dev: drm); |
290 | dma_free: |
291 | dma_free_wc(dev, size: 4096, cpu_addr: v3d->mmu_scratch, dma_addr: v3d->mmu_scratch_paddr); |
292 | return ret; |
293 | } |
294 | |
295 | static void v3d_platform_drm_remove(struct platform_device *pdev) |
296 | { |
297 | struct drm_device *drm = platform_get_drvdata(pdev); |
298 | struct v3d_dev *v3d = to_v3d_dev(dev: drm); |
299 | |
300 | drm_dev_unregister(dev: drm); |
301 | |
302 | v3d_gem_destroy(dev: drm); |
303 | |
304 | dma_free_wc(dev: v3d->drm.dev, size: 4096, cpu_addr: v3d->mmu_scratch, |
305 | dma_addr: v3d->mmu_scratch_paddr); |
306 | } |
307 | |
308 | static struct platform_driver v3d_platform_driver = { |
309 | .probe = v3d_platform_drm_probe, |
310 | .remove_new = v3d_platform_drm_remove, |
311 | .driver = { |
312 | .name = "v3d" , |
313 | .of_match_table = v3d_of_match, |
314 | }, |
315 | }; |
316 | |
317 | module_platform_driver(v3d_platform_driver); |
318 | |
319 | MODULE_ALIAS("platform:v3d-drm" ); |
320 | MODULE_DESCRIPTION("Broadcom V3D DRM Driver" ); |
321 | MODULE_AUTHOR("Eric Anholt <eric@anholt.net>" ); |
322 | MODULE_LICENSE("GPL v2" ); |
323 | |