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 | ||
13 | #include "drm.h" | |
de2ba664 AM |
14 | #include "gem.h" |
15 | ||
16 | static inline struct tegra_fb *to_tegra_fb(struct drm_framebuffer *fb) | |
17 | { | |
18 | return container_of(fb, struct tegra_fb, base); | |
19 | } | |
20 | ||
60c2f709 | 21 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
de2ba664 AM |
22 | static inline struct tegra_fbdev *to_tegra_fbdev(struct drm_fb_helper *helper) |
23 | { | |
24 | return container_of(helper, struct tegra_fbdev, base); | |
25 | } | |
60c2f709 | 26 | #endif |
de2ba664 AM |
27 | |
28 | struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, | |
29 | unsigned int index) | |
30 | { | |
31 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
32 | ||
33 | if (index >= drm_format_num_planes(framebuffer->pixel_format)) | |
34 | return NULL; | |
35 | ||
36 | return fb->planes[index]; | |
37 | } | |
38 | ||
db7fbdfd TR |
39 | bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer) |
40 | { | |
41 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
42 | ||
43 | if (fb->planes[0]->flags & TEGRA_BO_BOTTOM_UP) | |
44 | return true; | |
45 | ||
46 | return false; | |
47 | } | |
48 | ||
c134f019 TR |
49 | int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, |
50 | struct tegra_bo_tiling *tiling) | |
773af77f TR |
51 | { |
52 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
53 | ||
c134f019 TR |
54 | /* TODO: handle YUV formats? */ |
55 | *tiling = fb->planes[0]->tiling; | |
773af77f | 56 | |
c134f019 | 57 | return 0; |
773af77f TR |
58 | } |
59 | ||
de2ba664 AM |
60 | static void tegra_fb_destroy(struct drm_framebuffer *framebuffer) |
61 | { | |
62 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
63 | unsigned int i; | |
64 | ||
65 | for (i = 0; i < fb->num_planes; i++) { | |
66 | struct tegra_bo *bo = fb->planes[i]; | |
67 | ||
df06b759 TR |
68 | if (bo) { |
69 | if (bo->pages && bo->vaddr) | |
70 | vunmap(bo->vaddr); | |
71 | ||
de2ba664 | 72 | drm_gem_object_unreference_unlocked(&bo->gem); |
df06b759 | 73 | } |
de2ba664 AM |
74 | } |
75 | ||
76 | drm_framebuffer_cleanup(framebuffer); | |
77 | kfree(fb->planes); | |
78 | kfree(fb); | |
79 | } | |
80 | ||
81 | static int tegra_fb_create_handle(struct drm_framebuffer *framebuffer, | |
82 | struct drm_file *file, unsigned int *handle) | |
83 | { | |
84 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
85 | ||
86 | return drm_gem_handle_create(file, &fb->planes[0]->gem, handle); | |
87 | } | |
88 | ||
89 | static struct drm_framebuffer_funcs tegra_fb_funcs = { | |
90 | .destroy = tegra_fb_destroy, | |
91 | .create_handle = tegra_fb_create_handle, | |
92 | }; | |
93 | ||
94 | static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, | |
95 | struct drm_mode_fb_cmd2 *mode_cmd, | |
96 | struct tegra_bo **planes, | |
97 | unsigned int num_planes) | |
98 | { | |
99 | struct tegra_fb *fb; | |
100 | unsigned int i; | |
101 | int err; | |
102 | ||
103 | fb = kzalloc(sizeof(*fb), GFP_KERNEL); | |
104 | if (!fb) | |
105 | return ERR_PTR(-ENOMEM); | |
106 | ||
107 | fb->planes = kzalloc(num_planes * sizeof(*planes), GFP_KERNEL); | |
6b7c79d1 DC |
108 | if (!fb->planes) { |
109 | kfree(fb); | |
de2ba664 | 110 | return ERR_PTR(-ENOMEM); |
6b7c79d1 | 111 | } |
de2ba664 AM |
112 | |
113 | fb->num_planes = num_planes; | |
114 | ||
115 | drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd); | |
116 | ||
117 | for (i = 0; i < fb->num_planes; i++) | |
118 | fb->planes[i] = planes[i]; | |
119 | ||
120 | err = drm_framebuffer_init(drm, &fb->base, &tegra_fb_funcs); | |
121 | if (err < 0) { | |
122 | dev_err(drm->dev, "failed to initialize framebuffer: %d\n", | |
123 | err); | |
124 | kfree(fb->planes); | |
125 | kfree(fb); | |
126 | return ERR_PTR(err); | |
127 | } | |
128 | ||
129 | return fb; | |
130 | } | |
131 | ||
f9914214 TR |
132 | struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, |
133 | struct drm_file *file, | |
134 | struct drm_mode_fb_cmd2 *cmd) | |
de2ba664 AM |
135 | { |
136 | unsigned int hsub, vsub, i; | |
137 | struct tegra_bo *planes[4]; | |
138 | struct drm_gem_object *gem; | |
139 | struct tegra_fb *fb; | |
140 | int err; | |
141 | ||
142 | hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format); | |
143 | vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format); | |
144 | ||
145 | for (i = 0; i < drm_format_num_planes(cmd->pixel_format); i++) { | |
146 | unsigned int width = cmd->width / (i ? hsub : 1); | |
147 | unsigned int height = cmd->height / (i ? vsub : 1); | |
148 | unsigned int size, bpp; | |
149 | ||
150 | gem = drm_gem_object_lookup(drm, file, cmd->handles[i]); | |
151 | if (!gem) { | |
152 | err = -ENXIO; | |
153 | goto unreference; | |
154 | } | |
155 | ||
156 | bpp = drm_format_plane_cpp(cmd->pixel_format, i); | |
157 | ||
158 | size = (height - 1) * cmd->pitches[i] + | |
159 | width * bpp + cmd->offsets[i]; | |
160 | ||
161 | if (gem->size < size) { | |
162 | err = -EINVAL; | |
163 | goto unreference; | |
164 | } | |
165 | ||
166 | planes[i] = to_tegra_bo(gem); | |
167 | } | |
168 | ||
169 | fb = tegra_fb_alloc(drm, cmd, planes, i); | |
170 | if (IS_ERR(fb)) { | |
171 | err = PTR_ERR(fb); | |
172 | goto unreference; | |
173 | } | |
174 | ||
175 | return &fb->base; | |
176 | ||
177 | unreference: | |
178 | while (i--) | |
179 | drm_gem_object_unreference_unlocked(&planes[i]->gem); | |
d8f4a9ed | 180 | |
de2ba664 AM |
181 | return ERR_PTR(err); |
182 | } | |
183 | ||
60c2f709 | 184 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
de2ba664 AM |
185 | static struct fb_ops tegra_fb_ops = { |
186 | .owner = THIS_MODULE, | |
0f7d9052 AT |
187 | .fb_fillrect = drm_fb_helper_sys_fillrect, |
188 | .fb_copyarea = drm_fb_helper_sys_copyarea, | |
189 | .fb_imageblit = drm_fb_helper_sys_imageblit, | |
de2ba664 AM |
190 | .fb_check_var = drm_fb_helper_check_var, |
191 | .fb_set_par = drm_fb_helper_set_par, | |
192 | .fb_blank = drm_fb_helper_blank, | |
193 | .fb_pan_display = drm_fb_helper_pan_display, | |
194 | .fb_setcmap = drm_fb_helper_setcmap, | |
195 | }; | |
196 | ||
197 | static int tegra_fbdev_probe(struct drm_fb_helper *helper, | |
198 | struct drm_fb_helper_surface_size *sizes) | |
199 | { | |
200 | struct tegra_fbdev *fbdev = to_tegra_fbdev(helper); | |
d1f3e1e0 | 201 | struct tegra_drm *tegra = helper->dev->dev_private; |
de2ba664 AM |
202 | struct drm_device *drm = helper->dev; |
203 | struct drm_mode_fb_cmd2 cmd = { 0 }; | |
204 | unsigned int bytes_per_pixel; | |
205 | struct drm_framebuffer *fb; | |
206 | unsigned long offset; | |
207 | struct fb_info *info; | |
208 | struct tegra_bo *bo; | |
209 | size_t size; | |
210 | int err; | |
211 | ||
212 | bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); | |
213 | ||
214 | cmd.width = sizes->surface_width; | |
215 | cmd.height = sizes->surface_height; | |
d1f3e1e0 TR |
216 | cmd.pitches[0] = round_up(sizes->surface_width * bytes_per_pixel, |
217 | tegra->pitch_align); | |
de2ba664 AM |
218 | cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
219 | sizes->surface_depth); | |
220 | ||
221 | size = cmd.pitches[0] * cmd.height; | |
222 | ||
773af77f | 223 | bo = tegra_bo_create(drm, size, 0); |
de2ba664 AM |
224 | if (IS_ERR(bo)) |
225 | return PTR_ERR(bo); | |
226 | ||
0f7d9052 AT |
227 | info = drm_fb_helper_alloc_fbi(helper); |
228 | if (IS_ERR(info)) { | |
de2ba664 | 229 | dev_err(drm->dev, "failed to allocate framebuffer info\n"); |
b88f005e | 230 | drm_gem_object_unreference_unlocked(&bo->gem); |
0f7d9052 | 231 | return PTR_ERR(info); |
de2ba664 AM |
232 | } |
233 | ||
234 | fbdev->fb = tegra_fb_alloc(drm, &cmd, &bo, 1); | |
235 | if (IS_ERR(fbdev->fb)) { | |
de2ba664 | 236 | err = PTR_ERR(fbdev->fb); |
cb10c81f TR |
237 | dev_err(drm->dev, "failed to allocate DRM framebuffer: %d\n", |
238 | err); | |
b88f005e | 239 | drm_gem_object_unreference_unlocked(&bo->gem); |
de2ba664 AM |
240 | goto release; |
241 | } | |
242 | ||
243 | fb = &fbdev->fb->base; | |
244 | helper->fb = fb; | |
245 | helper->fbdev = info; | |
246 | ||
247 | info->par = helper; | |
248 | info->flags = FBINFO_FLAG_DEFAULT; | |
249 | info->fbops = &tegra_fb_ops; | |
250 | ||
de2ba664 AM |
251 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); |
252 | drm_fb_helper_fill_var(info, helper, fb->width, fb->height); | |
253 | ||
254 | offset = info->var.xoffset * bytes_per_pixel + | |
255 | info->var.yoffset * fb->pitches[0]; | |
256 | ||
df06b759 TR |
257 | if (bo->pages) { |
258 | bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP, | |
259 | pgprot_writecombine(PAGE_KERNEL)); | |
260 | if (!bo->vaddr) { | |
261 | dev_err(drm->dev, "failed to vmap() framebuffer\n"); | |
262 | err = -ENOMEM; | |
263 | goto destroy; | |
264 | } | |
265 | } | |
266 | ||
de2ba664 | 267 | drm->mode_config.fb_base = (resource_size_t)bo->paddr; |
9ab34151 | 268 | info->screen_base = (void __iomem *)bo->vaddr + offset; |
de2ba664 AM |
269 | info->screen_size = size; |
270 | info->fix.smem_start = (unsigned long)(bo->paddr + offset); | |
271 | info->fix.smem_len = size; | |
272 | ||
273 | return 0; | |
274 | ||
275 | destroy: | |
276 | drm_framebuffer_unregister_private(fb); | |
277 | tegra_fb_destroy(fb); | |
278 | release: | |
0f7d9052 | 279 | drm_fb_helper_release_fbi(helper); |
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 | ||
de2ba664 AM |
315 | err = drm_fb_helper_init(drm, &fbdev->base, num_crtc, max_connectors); |
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 | { |
de2ba664 | 344 | |
0f7d9052 AT |
345 | drm_fb_helper_unregister_fbi(&fbdev->base); |
346 | drm_fb_helper_release_fbi(&fbdev->base); | |
de2ba664 AM |
347 | |
348 | if (fbdev->fb) { | |
349 | drm_framebuffer_unregister_private(&fbdev->fb->base); | |
3feaf3e5 | 350 | drm_framebuffer_remove(&fbdev->fb->base); |
de2ba664 AM |
351 | } |
352 | ||
353 | drm_fb_helper_fini(&fbdev->base); | |
1d1e6fe9 | 354 | tegra_fbdev_free(fbdev); |
de2ba664 AM |
355 | } |
356 | ||
60c2f709 TR |
357 | void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) |
358 | { | |
5ea1f752 RC |
359 | if (fbdev) |
360 | drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base); | |
60c2f709 TR |
361 | } |
362 | ||
f9914214 | 363 | void tegra_fb_output_poll_changed(struct drm_device *drm) |
d8f4a9ed | 364 | { |
386a2a71 | 365 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 366 | |
386a2a71 TR |
367 | if (tegra->fbdev) |
368 | drm_fb_helper_hotplug_event(&tegra->fbdev->base); | |
d8f4a9ed | 369 | } |
60c2f709 | 370 | #endif |
d8f4a9ed | 371 | |
e2215321 | 372 | int tegra_drm_fb_prepare(struct drm_device *drm) |
d8f4a9ed | 373 | { |
60c2f709 | 374 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
386a2a71 | 375 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 376 | |
e2215321 | 377 | tegra->fbdev = tegra_fbdev_create(drm); |
60c2f709 TR |
378 | if (IS_ERR(tegra->fbdev)) |
379 | return PTR_ERR(tegra->fbdev); | |
380 | #endif | |
d8f4a9ed TR |
381 | |
382 | return 0; | |
383 | } | |
384 | ||
1d1e6fe9 TR |
385 | void tegra_drm_fb_free(struct drm_device *drm) |
386 | { | |
387 | #ifdef CONFIG_DRM_TEGRA_FBDEV | |
388 | struct tegra_drm *tegra = drm->dev_private; | |
389 | ||
390 | tegra_fbdev_free(tegra->fbdev); | |
391 | #endif | |
392 | } | |
393 | ||
e2215321 TR |
394 | int tegra_drm_fb_init(struct drm_device *drm) |
395 | { | |
396 | #ifdef CONFIG_DRM_TEGRA_FBDEV | |
397 | struct tegra_drm *tegra = drm->dev_private; | |
398 | int err; | |
399 | ||
400 | err = tegra_fbdev_init(tegra->fbdev, 32, drm->mode_config.num_crtc, | |
401 | drm->mode_config.num_connector); | |
402 | if (err < 0) | |
403 | return err; | |
404 | #endif | |
405 | ||
406 | return 0; | |
407 | } | |
408 | ||
d8f4a9ed TR |
409 | void tegra_drm_fb_exit(struct drm_device *drm) |
410 | { | |
60c2f709 | 411 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
386a2a71 | 412 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 413 | |
1d1e6fe9 | 414 | tegra_fbdev_exit(tegra->fbdev); |
60c2f709 | 415 | #endif |
d8f4a9ed | 416 | } |