Commit | Line | Data |
---|---|---|
d8f4a9ed | 1 | /* |
de2ba664 | 2 | * Copyright (C) 2012-2013 Avionic Design GmbH |
d8f4a9ed TR |
3 | * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. |
4 | * | |
de2ba664 AM |
5 | * Based on the KMS/FB CMA helpers |
6 | * Copyright (C) 2012 Analog Device Inc. | |
7 | * | |
d8f4a9ed TR |
8 | * This program is free software; you can redistribute it and/or modify |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | */ | |
12 | ||
986c58d1 TR |
13 | #include <linux/console.h> |
14 | ||
d8f4a9ed | 15 | #include "drm.h" |
de2ba664 AM |
16 | #include "gem.h" |
17 | ||
18 | static inline struct tegra_fb *to_tegra_fb(struct drm_framebuffer *fb) | |
19 | { | |
20 | return container_of(fb, struct tegra_fb, base); | |
21 | } | |
22 | ||
b110ef37 | 23 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
de2ba664 AM |
24 | static inline struct tegra_fbdev *to_tegra_fbdev(struct drm_fb_helper *helper) |
25 | { | |
26 | return container_of(helper, struct tegra_fbdev, base); | |
27 | } | |
60c2f709 | 28 | #endif |
de2ba664 AM |
29 | |
30 | struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, | |
31 | unsigned int index) | |
32 | { | |
33 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
34 | ||
35 | if (index >= drm_format_num_planes(framebuffer->pixel_format)) | |
36 | return NULL; | |
37 | ||
38 | return fb->planes[index]; | |
39 | } | |
40 | ||
db7fbdfd TR |
41 | bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer) |
42 | { | |
43 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
44 | ||
45 | if (fb->planes[0]->flags & TEGRA_BO_BOTTOM_UP) | |
46 | return true; | |
47 | ||
48 | return false; | |
49 | } | |
50 | ||
c134f019 TR |
51 | int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, |
52 | struct tegra_bo_tiling *tiling) | |
773af77f TR |
53 | { |
54 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
55 | ||
c134f019 TR |
56 | /* TODO: handle YUV formats? */ |
57 | *tiling = fb->planes[0]->tiling; | |
773af77f | 58 | |
c134f019 | 59 | return 0; |
773af77f TR |
60 | } |
61 | ||
de2ba664 AM |
62 | static void tegra_fb_destroy(struct drm_framebuffer *framebuffer) |
63 | { | |
64 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
65 | unsigned int i; | |
66 | ||
67 | for (i = 0; i < fb->num_planes; i++) { | |
68 | struct tegra_bo *bo = fb->planes[i]; | |
69 | ||
df06b759 | 70 | if (bo) { |
a63caf13 | 71 | if (bo->pages) |
df06b759 TR |
72 | vunmap(bo->vaddr); |
73 | ||
de2ba664 | 74 | drm_gem_object_unreference_unlocked(&bo->gem); |
df06b759 | 75 | } |
de2ba664 AM |
76 | } |
77 | ||
78 | drm_framebuffer_cleanup(framebuffer); | |
79 | kfree(fb->planes); | |
80 | kfree(fb); | |
81 | } | |
82 | ||
83 | static int tegra_fb_create_handle(struct drm_framebuffer *framebuffer, | |
84 | struct drm_file *file, unsigned int *handle) | |
85 | { | |
86 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
87 | ||
88 | return drm_gem_handle_create(file, &fb->planes[0]->gem, handle); | |
89 | } | |
90 | ||
4ecae785 | 91 | static const struct drm_framebuffer_funcs tegra_fb_funcs = { |
de2ba664 AM |
92 | .destroy = tegra_fb_destroy, |
93 | .create_handle = tegra_fb_create_handle, | |
94 | }; | |
95 | ||
96 | static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, | |
1eb83451 | 97 | const struct drm_mode_fb_cmd2 *mode_cmd, |
de2ba664 AM |
98 | struct tegra_bo **planes, |
99 | unsigned int num_planes) | |
100 | { | |
101 | struct tegra_fb *fb; | |
102 | unsigned int i; | |
103 | int err; | |
104 | ||
105 | fb = kzalloc(sizeof(*fb), GFP_KERNEL); | |
106 | if (!fb) | |
107 | return ERR_PTR(-ENOMEM); | |
108 | ||
109 | fb->planes = kzalloc(num_planes * sizeof(*planes), GFP_KERNEL); | |
6b7c79d1 DC |
110 | if (!fb->planes) { |
111 | kfree(fb); | |
de2ba664 | 112 | return ERR_PTR(-ENOMEM); |
6b7c79d1 | 113 | } |
de2ba664 AM |
114 | |
115 | fb->num_planes = num_planes; | |
116 | ||
117 | drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd); | |
118 | ||
119 | for (i = 0; i < fb->num_planes; i++) | |
120 | fb->planes[i] = planes[i]; | |
121 | ||
122 | err = drm_framebuffer_init(drm, &fb->base, &tegra_fb_funcs); | |
123 | if (err < 0) { | |
124 | dev_err(drm->dev, "failed to initialize framebuffer: %d\n", | |
125 | err); | |
126 | kfree(fb->planes); | |
127 | kfree(fb); | |
128 | return ERR_PTR(err); | |
129 | } | |
130 | ||
131 | return fb; | |
132 | } | |
133 | ||
f9914214 TR |
134 | struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, |
135 | struct drm_file *file, | |
1eb83451 | 136 | const struct drm_mode_fb_cmd2 *cmd) |
de2ba664 AM |
137 | { |
138 | unsigned int hsub, vsub, i; | |
139 | struct tegra_bo *planes[4]; | |
140 | struct drm_gem_object *gem; | |
141 | struct tegra_fb *fb; | |
142 | int err; | |
143 | ||
144 | hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format); | |
145 | vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format); | |
146 | ||
147 | for (i = 0; i < drm_format_num_planes(cmd->pixel_format); i++) { | |
148 | unsigned int width = cmd->width / (i ? hsub : 1); | |
149 | unsigned int height = cmd->height / (i ? vsub : 1); | |
150 | unsigned int size, bpp; | |
151 | ||
a8ad0bd8 | 152 | gem = drm_gem_object_lookup(file, cmd->handles[i]); |
de2ba664 AM |
153 | if (!gem) { |
154 | err = -ENXIO; | |
155 | goto unreference; | |
156 | } | |
157 | ||
158 | bpp = drm_format_plane_cpp(cmd->pixel_format, i); | |
159 | ||
160 | size = (height - 1) * cmd->pitches[i] + | |
161 | width * bpp + cmd->offsets[i]; | |
162 | ||
163 | if (gem->size < size) { | |
164 | err = -EINVAL; | |
165 | goto unreference; | |
166 | } | |
167 | ||
168 | planes[i] = to_tegra_bo(gem); | |
169 | } | |
170 | ||
171 | fb = tegra_fb_alloc(drm, cmd, planes, i); | |
172 | if (IS_ERR(fb)) { | |
173 | err = PTR_ERR(fb); | |
174 | goto unreference; | |
175 | } | |
176 | ||
177 | return &fb->base; | |
178 | ||
179 | unreference: | |
180 | while (i--) | |
181 | drm_gem_object_unreference_unlocked(&planes[i]->gem); | |
d8f4a9ed | 182 | |
de2ba664 AM |
183 | return ERR_PTR(err); |
184 | } | |
185 | ||
b110ef37 | 186 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
de2ba664 AM |
187 | static struct fb_ops tegra_fb_ops = { |
188 | .owner = THIS_MODULE, | |
0f7d9052 AT |
189 | .fb_fillrect = drm_fb_helper_sys_fillrect, |
190 | .fb_copyarea = drm_fb_helper_sys_copyarea, | |
191 | .fb_imageblit = drm_fb_helper_sys_imageblit, | |
de2ba664 AM |
192 | .fb_check_var = drm_fb_helper_check_var, |
193 | .fb_set_par = drm_fb_helper_set_par, | |
194 | .fb_blank = drm_fb_helper_blank, | |
195 | .fb_pan_display = drm_fb_helper_pan_display, | |
196 | .fb_setcmap = drm_fb_helper_setcmap, | |
197 | }; | |
198 | ||
199 | static int tegra_fbdev_probe(struct drm_fb_helper *helper, | |
200 | struct drm_fb_helper_surface_size *sizes) | |
201 | { | |
202 | struct tegra_fbdev *fbdev = to_tegra_fbdev(helper); | |
d1f3e1e0 | 203 | struct tegra_drm *tegra = helper->dev->dev_private; |
de2ba664 AM |
204 | struct drm_device *drm = helper->dev; |
205 | struct drm_mode_fb_cmd2 cmd = { 0 }; | |
206 | unsigned int bytes_per_pixel; | |
207 | struct drm_framebuffer *fb; | |
208 | unsigned long offset; | |
209 | struct fb_info *info; | |
210 | struct tegra_bo *bo; | |
211 | size_t size; | |
212 | int err; | |
213 | ||
214 | bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); | |
215 | ||
216 | cmd.width = sizes->surface_width; | |
217 | cmd.height = sizes->surface_height; | |
d1f3e1e0 TR |
218 | cmd.pitches[0] = round_up(sizes->surface_width * bytes_per_pixel, |
219 | tegra->pitch_align); | |
de2ba664 AM |
220 | cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
221 | sizes->surface_depth); | |
222 | ||
223 | size = cmd.pitches[0] * cmd.height; | |
224 | ||
773af77f | 225 | bo = tegra_bo_create(drm, size, 0); |
de2ba664 AM |
226 | if (IS_ERR(bo)) |
227 | return PTR_ERR(bo); | |
228 | ||
0f7d9052 AT |
229 | info = drm_fb_helper_alloc_fbi(helper); |
230 | if (IS_ERR(info)) { | |
de2ba664 | 231 | dev_err(drm->dev, "failed to allocate framebuffer info\n"); |
b88f005e | 232 | drm_gem_object_unreference_unlocked(&bo->gem); |
0f7d9052 | 233 | return PTR_ERR(info); |
de2ba664 AM |
234 | } |
235 | ||
236 | fbdev->fb = tegra_fb_alloc(drm, &cmd, &bo, 1); | |
237 | if (IS_ERR(fbdev->fb)) { | |
de2ba664 | 238 | err = PTR_ERR(fbdev->fb); |
cb10c81f TR |
239 | dev_err(drm->dev, "failed to allocate DRM framebuffer: %d\n", |
240 | err); | |
b88f005e | 241 | drm_gem_object_unreference_unlocked(&bo->gem); |
de2ba664 AM |
242 | goto release; |
243 | } | |
244 | ||
245 | fb = &fbdev->fb->base; | |
246 | helper->fb = fb; | |
247 | helper->fbdev = info; | |
248 | ||
249 | info->par = helper; | |
250 | info->flags = FBINFO_FLAG_DEFAULT; | |
251 | info->fbops = &tegra_fb_ops; | |
252 | ||
de2ba664 AM |
253 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); |
254 | drm_fb_helper_fill_var(info, helper, fb->width, fb->height); | |
255 | ||
256 | offset = info->var.xoffset * bytes_per_pixel + | |
257 | info->var.yoffset * fb->pitches[0]; | |
258 | ||
df06b759 TR |
259 | if (bo->pages) { |
260 | bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP, | |
261 | pgprot_writecombine(PAGE_KERNEL)); | |
262 | if (!bo->vaddr) { | |
263 | dev_err(drm->dev, "failed to vmap() framebuffer\n"); | |
264 | err = -ENOMEM; | |
265 | goto destroy; | |
266 | } | |
267 | } | |
268 | ||
de2ba664 | 269 | drm->mode_config.fb_base = (resource_size_t)bo->paddr; |
9ab34151 | 270 | info->screen_base = (void __iomem *)bo->vaddr + offset; |
de2ba664 AM |
271 | info->screen_size = size; |
272 | info->fix.smem_start = (unsigned long)(bo->paddr + offset); | |
273 | info->fix.smem_len = size; | |
274 | ||
275 | return 0; | |
276 | ||
277 | destroy: | |
278 | drm_framebuffer_unregister_private(fb); | |
279 | tegra_fb_destroy(fb); | |
280 | release: | |
0f7d9052 | 281 | drm_fb_helper_release_fbi(helper); |
de2ba664 AM |
282 | return err; |
283 | } | |
284 | ||
3a493879 | 285 | static const struct drm_fb_helper_funcs tegra_fb_helper_funcs = { |
de2ba664 AM |
286 | .fb_probe = tegra_fbdev_probe, |
287 | }; | |
288 | ||
e2215321 | 289 | static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm) |
de2ba664 | 290 | { |
de2ba664 | 291 | struct tegra_fbdev *fbdev; |
de2ba664 AM |
292 | |
293 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
294 | if (!fbdev) { | |
295 | dev_err(drm->dev, "failed to allocate DRM fbdev\n"); | |
296 | return ERR_PTR(-ENOMEM); | |
297 | } | |
298 | ||
10a23102 | 299 | drm_fb_helper_prepare(drm, &fbdev->base, &tegra_fb_helper_funcs); |
de2ba664 | 300 | |
e2215321 TR |
301 | return fbdev; |
302 | } | |
303 | ||
1d1e6fe9 TR |
304 | static void tegra_fbdev_free(struct tegra_fbdev *fbdev) |
305 | { | |
306 | kfree(fbdev); | |
307 | } | |
308 | ||
e2215321 TR |
309 | static int tegra_fbdev_init(struct tegra_fbdev *fbdev, |
310 | unsigned int preferred_bpp, | |
311 | unsigned int num_crtc, | |
312 | unsigned int max_connectors) | |
313 | { | |
314 | struct drm_device *drm = fbdev->base.dev; | |
315 | int err; | |
316 | ||
de2ba664 AM |
317 | err = drm_fb_helper_init(drm, &fbdev->base, num_crtc, max_connectors); |
318 | if (err < 0) { | |
cb10c81f TR |
319 | dev_err(drm->dev, "failed to initialize DRM FB helper: %d\n", |
320 | err); | |
e2215321 | 321 | return err; |
de2ba664 AM |
322 | } |
323 | ||
324 | err = drm_fb_helper_single_add_all_connectors(&fbdev->base); | |
325 | if (err < 0) { | |
cb10c81f | 326 | dev_err(drm->dev, "failed to add connectors: %d\n", err); |
de2ba664 AM |
327 | goto fini; |
328 | } | |
329 | ||
de2ba664 AM |
330 | err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp); |
331 | if (err < 0) { | |
cb10c81f TR |
332 | dev_err(drm->dev, "failed to set initial configuration: %d\n", |
333 | err); | |
de2ba664 AM |
334 | goto fini; |
335 | } | |
336 | ||
e2215321 | 337 | return 0; |
de2ba664 AM |
338 | |
339 | fini: | |
340 | drm_fb_helper_fini(&fbdev->base); | |
e2215321 | 341 | return err; |
de2ba664 AM |
342 | } |
343 | ||
1d1e6fe9 | 344 | static void tegra_fbdev_exit(struct tegra_fbdev *fbdev) |
de2ba664 | 345 | { |
0f7d9052 AT |
346 | drm_fb_helper_unregister_fbi(&fbdev->base); |
347 | drm_fb_helper_release_fbi(&fbdev->base); | |
de2ba664 AM |
348 | |
349 | if (fbdev->fb) { | |
350 | drm_framebuffer_unregister_private(&fbdev->fb->base); | |
3feaf3e5 | 351 | drm_framebuffer_remove(&fbdev->fb->base); |
de2ba664 AM |
352 | } |
353 | ||
354 | drm_fb_helper_fini(&fbdev->base); | |
1d1e6fe9 | 355 | tegra_fbdev_free(fbdev); |
de2ba664 AM |
356 | } |
357 | ||
60c2f709 TR |
358 | void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) |
359 | { | |
5ea1f752 RC |
360 | if (fbdev) |
361 | drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base); | |
60c2f709 TR |
362 | } |
363 | ||
f9914214 | 364 | void tegra_fb_output_poll_changed(struct drm_device *drm) |
d8f4a9ed | 365 | { |
386a2a71 | 366 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 367 | |
386a2a71 TR |
368 | if (tegra->fbdev) |
369 | drm_fb_helper_hotplug_event(&tegra->fbdev->base); | |
d8f4a9ed | 370 | } |
60c2f709 | 371 | #endif |
d8f4a9ed | 372 | |
e2215321 | 373 | int tegra_drm_fb_prepare(struct drm_device *drm) |
d8f4a9ed | 374 | { |
b110ef37 | 375 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
386a2a71 | 376 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 377 | |
e2215321 | 378 | tegra->fbdev = tegra_fbdev_create(drm); |
60c2f709 TR |
379 | if (IS_ERR(tegra->fbdev)) |
380 | return PTR_ERR(tegra->fbdev); | |
381 | #endif | |
d8f4a9ed TR |
382 | |
383 | return 0; | |
384 | } | |
385 | ||
1d1e6fe9 TR |
386 | void tegra_drm_fb_free(struct drm_device *drm) |
387 | { | |
b110ef37 | 388 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
1d1e6fe9 TR |
389 | struct tegra_drm *tegra = drm->dev_private; |
390 | ||
391 | tegra_fbdev_free(tegra->fbdev); | |
392 | #endif | |
393 | } | |
394 | ||
e2215321 TR |
395 | int tegra_drm_fb_init(struct drm_device *drm) |
396 | { | |
b110ef37 | 397 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
e2215321 TR |
398 | struct tegra_drm *tegra = drm->dev_private; |
399 | int err; | |
400 | ||
401 | err = tegra_fbdev_init(tegra->fbdev, 32, drm->mode_config.num_crtc, | |
402 | drm->mode_config.num_connector); | |
403 | if (err < 0) | |
404 | return err; | |
405 | #endif | |
406 | ||
407 | return 0; | |
408 | } | |
409 | ||
d8f4a9ed TR |
410 | void tegra_drm_fb_exit(struct drm_device *drm) |
411 | { | |
b110ef37 | 412 | #ifdef CONFIG_DRM_FBDEV_EMULATION |
386a2a71 | 413 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 414 | |
1d1e6fe9 | 415 | tegra_fbdev_exit(tegra->fbdev); |
60c2f709 | 416 | #endif |
d8f4a9ed | 417 | } |
986c58d1 TR |
418 | |
419 | void tegra_drm_fb_suspend(struct drm_device *drm) | |
420 | { | |
421 | #ifdef CONFIG_DRM_FBDEV_EMULATION | |
422 | struct tegra_drm *tegra = drm->dev_private; | |
423 | ||
424 | console_lock(); | |
425 | drm_fb_helper_set_suspend(&tegra->fbdev->base, 1); | |
426 | console_unlock(); | |
427 | #endif | |
428 | } | |
429 | ||
430 | void tegra_drm_fb_resume(struct drm_device *drm) | |
431 | { | |
432 | #ifdef CONFIG_DRM_FBDEV_EMULATION | |
433 | struct tegra_drm *tegra = drm->dev_private; | |
434 | ||
435 | console_lock(); | |
436 | drm_fb_helper_set_suspend(&tegra->fbdev->base, 0); | |
437 | console_unlock(); | |
438 | #endif | |
439 | } |