]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
d8f4a9ed | 2 | /* |
de2ba664 | 3 | * Copyright (C) 2012-2013 Avionic Design GmbH |
d8f4a9ed TR |
4 | * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. |
5 | * | |
de2ba664 AM |
6 | * Based on the KMS/FB CMA helpers |
7 | * Copyright (C) 2012 Analog Device Inc. | |
d8f4a9ed TR |
8 | */ |
9 | ||
986c58d1 TR |
10 | #include <linux/console.h> |
11 | ||
d8f4a9ed | 12 | #include "drm.h" |
de2ba664 | 13 | #include "gem.h" |
0bc6af00 | 14 | #include <drm/drm_gem_framebuffer_helper.h> |
fcd70cd3 | 15 | #include <drm/drm_modeset_helper.h> |
de2ba664 | 16 | |
b110ef37 | 17 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
de2ba664 AM |
18 | static inline struct tegra_fbdev *to_tegra_fbdev(struct drm_fb_helper *helper) |
19 | { | |
20 | return container_of(helper, struct tegra_fbdev, base); | |
21 | } | |
60c2f709 | 22 | #endif |
de2ba664 AM |
23 | |
24 | struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, | |
25 | unsigned int index) | |
26 | { | |
0bc6af00 | 27 | return to_tegra_bo(drm_gem_fb_get_obj(framebuffer, index)); |
de2ba664 AM |
28 | } |
29 | ||
db7fbdfd TR |
30 | bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer) |
31 | { | |
0bc6af00 | 32 | struct tegra_bo *bo = tegra_fb_get_plane(framebuffer, 0); |
db7fbdfd | 33 | |
0bc6af00 | 34 | if (bo->flags & TEGRA_BO_BOTTOM_UP) |
db7fbdfd TR |
35 | return true; |
36 | ||
37 | return false; | |
38 | } | |
39 | ||
c134f019 TR |
40 | int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, |
41 | struct tegra_bo_tiling *tiling) | |
773af77f | 42 | { |
0bc6af00 | 43 | uint64_t modifier = framebuffer->modifier; |
5e91144d | 44 | |
268892cb | 45 | switch (modifier) { |
4ae4b5c0 TR |
46 | case DRM_FORMAT_MOD_LINEAR: |
47 | tiling->mode = TEGRA_BO_TILING_MODE_PITCH; | |
48 | tiling->value = 0; | |
49 | break; | |
50 | ||
268892cb | 51 | case DRM_FORMAT_MOD_NVIDIA_TEGRA_TILED: |
5e91144d AC |
52 | tiling->mode = TEGRA_BO_TILING_MODE_TILED; |
53 | tiling->value = 0; | |
54 | break; | |
55 | ||
268892cb | 56 | case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(0): |
5e91144d | 57 | tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; |
268892cb TR |
58 | tiling->value = 0; |
59 | break; | |
60 | ||
61 | case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(1): | |
62 | tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; | |
63 | tiling->value = 1; | |
64 | break; | |
65 | ||
66 | case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(2): | |
67 | tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; | |
68 | tiling->value = 2; | |
69 | break; | |
70 | ||
71 | case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(3): | |
72 | tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; | |
73 | tiling->value = 3; | |
74 | break; | |
75 | ||
76 | case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(4): | |
77 | tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; | |
78 | tiling->value = 4; | |
79 | break; | |
80 | ||
81 | case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(5): | |
82 | tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; | |
83 | tiling->value = 5; | |
5e91144d AC |
84 | break; |
85 | ||
86 | default: | |
4ae4b5c0 | 87 | return -EINVAL; |
5e91144d | 88 | } |
773af77f | 89 | |
c134f019 | 90 | return 0; |
773af77f TR |
91 | } |
92 | ||
4ecae785 | 93 | static const struct drm_framebuffer_funcs tegra_fb_funcs = { |
5cb8b996 | 94 | .destroy = drm_gem_fb_destroy, |
0bc6af00 | 95 | .create_handle = drm_gem_fb_create_handle, |
de2ba664 AM |
96 | }; |
97 | ||
dbc33c7d DS |
98 | static struct drm_framebuffer *tegra_fb_alloc(struct drm_device *drm, |
99 | const struct drm_mode_fb_cmd2 *mode_cmd, | |
100 | struct tegra_bo **planes, | |
101 | unsigned int num_planes) | |
de2ba664 | 102 | { |
dbc33c7d | 103 | struct drm_framebuffer *fb; |
de2ba664 AM |
104 | unsigned int i; |
105 | int err; | |
106 | ||
107 | fb = kzalloc(sizeof(*fb), GFP_KERNEL); | |
108 | if (!fb) | |
109 | return ERR_PTR(-ENOMEM); | |
110 | ||
dbc33c7d | 111 | drm_helper_mode_fill_fb_struct(drm, fb, mode_cmd); |
de2ba664 | 112 | |
dbc33c7d DS |
113 | for (i = 0; i < fb->format->num_planes; i++) |
114 | fb->obj[i] = &planes[i]->gem; | |
de2ba664 | 115 | |
dbc33c7d | 116 | err = drm_framebuffer_init(drm, fb, &tegra_fb_funcs); |
de2ba664 AM |
117 | if (err < 0) { |
118 | dev_err(drm->dev, "failed to initialize framebuffer: %d\n", | |
119 | err); | |
de2ba664 AM |
120 | kfree(fb); |
121 | return ERR_PTR(err); | |
122 | } | |
123 | ||
124 | return fb; | |
125 | } | |
126 | ||
f9914214 TR |
127 | struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, |
128 | struct drm_file *file, | |
1eb83451 | 129 | const struct drm_mode_fb_cmd2 *cmd) |
de2ba664 | 130 | { |
05c452c1 | 131 | const struct drm_format_info *info = drm_get_format_info(drm, cmd); |
de2ba664 AM |
132 | struct tegra_bo *planes[4]; |
133 | struct drm_gem_object *gem; | |
dbc33c7d | 134 | struct drm_framebuffer *fb; |
f3e9632c | 135 | unsigned int i; |
de2ba664 AM |
136 | int err; |
137 | ||
05c452c1 | 138 | for (i = 0; i < info->num_planes; i++) { |
f3e9632c MR |
139 | unsigned int width = cmd->width / (i ? info->hsub : 1); |
140 | unsigned int height = cmd->height / (i ? info->vsub : 1); | |
de2ba664 AM |
141 | unsigned int size, bpp; |
142 | ||
a8ad0bd8 | 143 | gem = drm_gem_object_lookup(file, cmd->handles[i]); |
de2ba664 AM |
144 | if (!gem) { |
145 | err = -ENXIO; | |
146 | goto unreference; | |
147 | } | |
148 | ||
b0f986b4 | 149 | bpp = info->cpp[i]; |
de2ba664 AM |
150 | |
151 | size = (height - 1) * cmd->pitches[i] + | |
152 | width * bpp + cmd->offsets[i]; | |
153 | ||
154 | if (gem->size < size) { | |
155 | err = -EINVAL; | |
156 | goto unreference; | |
157 | } | |
158 | ||
159 | planes[i] = to_tegra_bo(gem); | |
160 | } | |
161 | ||
162 | fb = tegra_fb_alloc(drm, cmd, planes, i); | |
163 | if (IS_ERR(fb)) { | |
164 | err = PTR_ERR(fb); | |
165 | goto unreference; | |
166 | } | |
167 | ||
dbc33c7d | 168 | return fb; |
de2ba664 AM |
169 | |
170 | unreference: | |
171 | while (i--) | |
7664b2fa | 172 | drm_gem_object_put_unlocked(&planes[i]->gem); |
d8f4a9ed | 173 | |
de2ba664 AM |
174 | return ERR_PTR(err); |
175 | } | |
176 | ||
b110ef37 | 177 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
b8f3f500 TR |
178 | static int tegra_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
179 | { | |
180 | struct drm_fb_helper *helper = info->par; | |
181 | struct tegra_bo *bo; | |
182 | int err; | |
183 | ||
184 | bo = tegra_fb_get_plane(helper->fb, 0); | |
185 | ||
186 | err = drm_gem_mmap_obj(&bo->gem, bo->gem.size, vma); | |
187 | if (err < 0) | |
188 | return err; | |
189 | ||
190 | return __tegra_gem_mmap(&bo->gem, vma); | |
191 | } | |
192 | ||
de2ba664 AM |
193 | static struct fb_ops tegra_fb_ops = { |
194 | .owner = THIS_MODULE, | |
902c255b | 195 | DRM_FB_HELPER_DEFAULT_OPS, |
0f7d9052 AT |
196 | .fb_fillrect = drm_fb_helper_sys_fillrect, |
197 | .fb_copyarea = drm_fb_helper_sys_copyarea, | |
198 | .fb_imageblit = drm_fb_helper_sys_imageblit, | |
b8f3f500 | 199 | .fb_mmap = tegra_fb_mmap, |
de2ba664 AM |
200 | }; |
201 | ||
202 | static int tegra_fbdev_probe(struct drm_fb_helper *helper, | |
203 | struct drm_fb_helper_surface_size *sizes) | |
204 | { | |
205 | struct tegra_fbdev *fbdev = to_tegra_fbdev(helper); | |
d1f3e1e0 | 206 | struct tegra_drm *tegra = helper->dev->dev_private; |
de2ba664 AM |
207 | struct drm_device *drm = helper->dev; |
208 | struct drm_mode_fb_cmd2 cmd = { 0 }; | |
209 | unsigned int bytes_per_pixel; | |
210 | struct drm_framebuffer *fb; | |
211 | unsigned long offset; | |
212 | struct fb_info *info; | |
213 | struct tegra_bo *bo; | |
214 | size_t size; | |
215 | int err; | |
216 | ||
217 | bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); | |
218 | ||
219 | cmd.width = sizes->surface_width; | |
220 | cmd.height = sizes->surface_height; | |
d1f3e1e0 TR |
221 | cmd.pitches[0] = round_up(sizes->surface_width * bytes_per_pixel, |
222 | tegra->pitch_align); | |
71835caa | 223 | |
de2ba664 AM |
224 | cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
225 | sizes->surface_depth); | |
226 | ||
227 | size = cmd.pitches[0] * cmd.height; | |
228 | ||
773af77f | 229 | bo = tegra_bo_create(drm, size, 0); |
de2ba664 AM |
230 | if (IS_ERR(bo)) |
231 | return PTR_ERR(bo); | |
232 | ||
0f7d9052 AT |
233 | info = drm_fb_helper_alloc_fbi(helper); |
234 | if (IS_ERR(info)) { | |
de2ba664 | 235 | dev_err(drm->dev, "failed to allocate framebuffer info\n"); |
7664b2fa | 236 | drm_gem_object_put_unlocked(&bo->gem); |
0f7d9052 | 237 | return PTR_ERR(info); |
de2ba664 AM |
238 | } |
239 | ||
240 | fbdev->fb = tegra_fb_alloc(drm, &cmd, &bo, 1); | |
241 | if (IS_ERR(fbdev->fb)) { | |
de2ba664 | 242 | err = PTR_ERR(fbdev->fb); |
cb10c81f TR |
243 | dev_err(drm->dev, "failed to allocate DRM framebuffer: %d\n", |
244 | err); | |
7664b2fa | 245 | drm_gem_object_put_unlocked(&bo->gem); |
da7bdda2 | 246 | return PTR_ERR(fbdev->fb); |
de2ba664 AM |
247 | } |
248 | ||
dbc33c7d | 249 | fb = fbdev->fb; |
de2ba664 AM |
250 | helper->fb = fb; |
251 | helper->fbdev = info; | |
252 | ||
de2ba664 AM |
253 | info->fbops = &tegra_fb_ops; |
254 | ||
4a536934 | 255 | drm_fb_helper_fill_info(info, helper, sizes); |
de2ba664 AM |
256 | |
257 | offset = info->var.xoffset * bytes_per_pixel + | |
258 | info->var.yoffset * fb->pitches[0]; | |
259 | ||
df06b759 TR |
260 | if (bo->pages) { |
261 | bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP, | |
262 | pgprot_writecombine(PAGE_KERNEL)); | |
263 | if (!bo->vaddr) { | |
264 | dev_err(drm->dev, "failed to vmap() framebuffer\n"); | |
265 | err = -ENOMEM; | |
266 | goto destroy; | |
267 | } | |
268 | } | |
269 | ||
de2ba664 | 270 | drm->mode_config.fb_base = (resource_size_t)bo->paddr; |
9ab34151 | 271 | info->screen_base = (void __iomem *)bo->vaddr + offset; |
de2ba664 AM |
272 | info->screen_size = size; |
273 | info->fix.smem_start = (unsigned long)(bo->paddr + offset); | |
274 | info->fix.smem_len = size; | |
275 | ||
276 | return 0; | |
277 | ||
278 | destroy: | |
3e7d2fdd | 279 | drm_framebuffer_remove(fb); |
de2ba664 AM |
280 | return err; |
281 | } | |
282 | ||
3a493879 | 283 | static const struct drm_fb_helper_funcs tegra_fb_helper_funcs = { |
de2ba664 AM |
284 | .fb_probe = tegra_fbdev_probe, |
285 | }; | |
286 | ||
e2215321 | 287 | static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm) |
de2ba664 | 288 | { |
de2ba664 | 289 | struct tegra_fbdev *fbdev; |
de2ba664 AM |
290 | |
291 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
292 | if (!fbdev) { | |
293 | dev_err(drm->dev, "failed to allocate DRM fbdev\n"); | |
294 | return ERR_PTR(-ENOMEM); | |
295 | } | |
296 | ||
10a23102 | 297 | drm_fb_helper_prepare(drm, &fbdev->base, &tegra_fb_helper_funcs); |
de2ba664 | 298 | |
e2215321 TR |
299 | return fbdev; |
300 | } | |
301 | ||
1d1e6fe9 TR |
302 | static void tegra_fbdev_free(struct tegra_fbdev *fbdev) |
303 | { | |
304 | kfree(fbdev); | |
305 | } | |
306 | ||
e2215321 TR |
307 | static int tegra_fbdev_init(struct tegra_fbdev *fbdev, |
308 | unsigned int preferred_bpp, | |
309 | unsigned int num_crtc, | |
310 | unsigned int max_connectors) | |
311 | { | |
312 | struct drm_device *drm = fbdev->base.dev; | |
313 | int err; | |
314 | ||
e4563f6b | 315 | err = drm_fb_helper_init(drm, &fbdev->base, max_connectors); |
de2ba664 | 316 | if (err < 0) { |
cb10c81f TR |
317 | dev_err(drm->dev, "failed to initialize DRM FB helper: %d\n", |
318 | err); | |
e2215321 | 319 | return err; |
de2ba664 AM |
320 | } |
321 | ||
322 | err = drm_fb_helper_single_add_all_connectors(&fbdev->base); | |
323 | if (err < 0) { | |
cb10c81f | 324 | dev_err(drm->dev, "failed to add connectors: %d\n", err); |
de2ba664 AM |
325 | goto fini; |
326 | } | |
327 | ||
de2ba664 AM |
328 | err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp); |
329 | if (err < 0) { | |
cb10c81f TR |
330 | dev_err(drm->dev, "failed to set initial configuration: %d\n", |
331 | err); | |
de2ba664 AM |
332 | goto fini; |
333 | } | |
334 | ||
e2215321 | 335 | return 0; |
de2ba664 AM |
336 | |
337 | fini: | |
338 | drm_fb_helper_fini(&fbdev->base); | |
e2215321 | 339 | return err; |
de2ba664 AM |
340 | } |
341 | ||
1d1e6fe9 | 342 | static void tegra_fbdev_exit(struct tegra_fbdev *fbdev) |
de2ba664 | 343 | { |
0f7d9052 | 344 | drm_fb_helper_unregister_fbi(&fbdev->base); |
de2ba664 | 345 | |
c34a997d DS |
346 | if (fbdev->fb) { |
347 | struct tegra_bo *bo = tegra_fb_get_plane(fbdev->fb, 0); | |
348 | ||
349 | /* Undo the special mapping we made in fbdev probe. */ | |
350 | if (bo && bo->pages) { | |
351 | vunmap(bo->vaddr); | |
53f1e062 | 352 | bo->vaddr = NULL; |
c34a997d DS |
353 | } |
354 | ||
dbc33c7d | 355 | drm_framebuffer_remove(fbdev->fb); |
c34a997d | 356 | } |
de2ba664 AM |
357 | |
358 | drm_fb_helper_fini(&fbdev->base); | |
1d1e6fe9 | 359 | tegra_fbdev_free(fbdev); |
de2ba664 | 360 | } |
60c2f709 | 361 | #endif |
d8f4a9ed | 362 | |
e2215321 | 363 | int tegra_drm_fb_prepare(struct drm_device *drm) |
d8f4a9ed | 364 | { |
b110ef37 | 365 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
386a2a71 | 366 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 367 | |
e2215321 | 368 | tegra->fbdev = tegra_fbdev_create(drm); |
60c2f709 TR |
369 | if (IS_ERR(tegra->fbdev)) |
370 | return PTR_ERR(tegra->fbdev); | |
371 | #endif | |
d8f4a9ed TR |
372 | |
373 | return 0; | |
374 | } | |
375 | ||
1d1e6fe9 TR |
376 | void tegra_drm_fb_free(struct drm_device *drm) |
377 | { | |
b110ef37 | 378 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
1d1e6fe9 TR |
379 | struct tegra_drm *tegra = drm->dev_private; |
380 | ||
381 | tegra_fbdev_free(tegra->fbdev); | |
382 | #endif | |
383 | } | |
384 | ||
e2215321 TR |
385 | int tegra_drm_fb_init(struct drm_device *drm) |
386 | { | |
b110ef37 | 387 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
e2215321 TR |
388 | struct tegra_drm *tegra = drm->dev_private; |
389 | int err; | |
390 | ||
391 | err = tegra_fbdev_init(tegra->fbdev, 32, drm->mode_config.num_crtc, | |
392 | drm->mode_config.num_connector); | |
393 | if (err < 0) | |
394 | return err; | |
395 | #endif | |
396 | ||
397 | return 0; | |
398 | } | |
399 | ||
d8f4a9ed TR |
400 | void tegra_drm_fb_exit(struct drm_device *drm) |
401 | { | |
b110ef37 | 402 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
386a2a71 | 403 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 404 | |
1d1e6fe9 | 405 | tegra_fbdev_exit(tegra->fbdev); |
60c2f709 | 406 | #endif |
d8f4a9ed | 407 | } |