]>
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 | ||
21 | static inline struct tegra_fbdev *to_tegra_fbdev(struct drm_fb_helper *helper) | |
22 | { | |
23 | return container_of(helper, struct tegra_fbdev, base); | |
24 | } | |
25 | ||
26 | struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, | |
27 | unsigned int index) | |
28 | { | |
29 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
30 | ||
31 | if (index >= drm_format_num_planes(framebuffer->pixel_format)) | |
32 | return NULL; | |
33 | ||
34 | return fb->planes[index]; | |
35 | } | |
36 | ||
db7fbdfd TR |
37 | bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer) |
38 | { | |
39 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
40 | ||
41 | if (fb->planes[0]->flags & TEGRA_BO_BOTTOM_UP) | |
42 | return true; | |
43 | ||
44 | return false; | |
45 | } | |
46 | ||
773af77f TR |
47 | bool tegra_fb_is_tiled(struct drm_framebuffer *framebuffer) |
48 | { | |
49 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
50 | ||
51 | if (fb->planes[0]->flags & TEGRA_BO_TILED) | |
52 | return true; | |
53 | ||
54 | return false; | |
55 | } | |
56 | ||
de2ba664 AM |
57 | static void tegra_fb_destroy(struct drm_framebuffer *framebuffer) |
58 | { | |
59 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
60 | unsigned int i; | |
61 | ||
62 | for (i = 0; i < fb->num_planes; i++) { | |
63 | struct tegra_bo *bo = fb->planes[i]; | |
64 | ||
65 | if (bo) | |
66 | drm_gem_object_unreference_unlocked(&bo->gem); | |
67 | } | |
68 | ||
69 | drm_framebuffer_cleanup(framebuffer); | |
70 | kfree(fb->planes); | |
71 | kfree(fb); | |
72 | } | |
73 | ||
74 | static int tegra_fb_create_handle(struct drm_framebuffer *framebuffer, | |
75 | struct drm_file *file, unsigned int *handle) | |
76 | { | |
77 | struct tegra_fb *fb = to_tegra_fb(framebuffer); | |
78 | ||
79 | return drm_gem_handle_create(file, &fb->planes[0]->gem, handle); | |
80 | } | |
81 | ||
82 | static struct drm_framebuffer_funcs tegra_fb_funcs = { | |
83 | .destroy = tegra_fb_destroy, | |
84 | .create_handle = tegra_fb_create_handle, | |
85 | }; | |
86 | ||
87 | static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, | |
88 | struct drm_mode_fb_cmd2 *mode_cmd, | |
89 | struct tegra_bo **planes, | |
90 | unsigned int num_planes) | |
91 | { | |
92 | struct tegra_fb *fb; | |
93 | unsigned int i; | |
94 | int err; | |
95 | ||
96 | fb = kzalloc(sizeof(*fb), GFP_KERNEL); | |
97 | if (!fb) | |
98 | return ERR_PTR(-ENOMEM); | |
99 | ||
100 | fb->planes = kzalloc(num_planes * sizeof(*planes), GFP_KERNEL); | |
101 | if (!fb->planes) | |
102 | return ERR_PTR(-ENOMEM); | |
103 | ||
104 | fb->num_planes = num_planes; | |
105 | ||
106 | drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd); | |
107 | ||
108 | for (i = 0; i < fb->num_planes; i++) | |
109 | fb->planes[i] = planes[i]; | |
110 | ||
111 | err = drm_framebuffer_init(drm, &fb->base, &tegra_fb_funcs); | |
112 | if (err < 0) { | |
113 | dev_err(drm->dev, "failed to initialize framebuffer: %d\n", | |
114 | err); | |
115 | kfree(fb->planes); | |
116 | kfree(fb); | |
117 | return ERR_PTR(err); | |
118 | } | |
119 | ||
120 | return fb; | |
121 | } | |
122 | ||
123 | static struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, | |
124 | struct drm_file *file, | |
125 | struct drm_mode_fb_cmd2 *cmd) | |
126 | { | |
127 | unsigned int hsub, vsub, i; | |
128 | struct tegra_bo *planes[4]; | |
129 | struct drm_gem_object *gem; | |
130 | struct tegra_fb *fb; | |
131 | int err; | |
132 | ||
133 | hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format); | |
134 | vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format); | |
135 | ||
136 | for (i = 0; i < drm_format_num_planes(cmd->pixel_format); i++) { | |
137 | unsigned int width = cmd->width / (i ? hsub : 1); | |
138 | unsigned int height = cmd->height / (i ? vsub : 1); | |
139 | unsigned int size, bpp; | |
140 | ||
141 | gem = drm_gem_object_lookup(drm, file, cmd->handles[i]); | |
142 | if (!gem) { | |
143 | err = -ENXIO; | |
144 | goto unreference; | |
145 | } | |
146 | ||
147 | bpp = drm_format_plane_cpp(cmd->pixel_format, i); | |
148 | ||
149 | size = (height - 1) * cmd->pitches[i] + | |
150 | width * bpp + cmd->offsets[i]; | |
151 | ||
152 | if (gem->size < size) { | |
153 | err = -EINVAL; | |
154 | goto unreference; | |
155 | } | |
156 | ||
157 | planes[i] = to_tegra_bo(gem); | |
158 | } | |
159 | ||
160 | fb = tegra_fb_alloc(drm, cmd, planes, i); | |
161 | if (IS_ERR(fb)) { | |
162 | err = PTR_ERR(fb); | |
163 | goto unreference; | |
164 | } | |
165 | ||
166 | return &fb->base; | |
167 | ||
168 | unreference: | |
169 | while (i--) | |
170 | drm_gem_object_unreference_unlocked(&planes[i]->gem); | |
d8f4a9ed | 171 | |
de2ba664 AM |
172 | return ERR_PTR(err); |
173 | } | |
174 | ||
175 | static struct fb_ops tegra_fb_ops = { | |
176 | .owner = THIS_MODULE, | |
177 | .fb_fillrect = sys_fillrect, | |
178 | .fb_copyarea = sys_copyarea, | |
179 | .fb_imageblit = sys_imageblit, | |
180 | .fb_check_var = drm_fb_helper_check_var, | |
181 | .fb_set_par = drm_fb_helper_set_par, | |
182 | .fb_blank = drm_fb_helper_blank, | |
183 | .fb_pan_display = drm_fb_helper_pan_display, | |
184 | .fb_setcmap = drm_fb_helper_setcmap, | |
185 | }; | |
186 | ||
187 | static int tegra_fbdev_probe(struct drm_fb_helper *helper, | |
188 | struct drm_fb_helper_surface_size *sizes) | |
189 | { | |
190 | struct tegra_fbdev *fbdev = to_tegra_fbdev(helper); | |
191 | struct drm_device *drm = helper->dev; | |
192 | struct drm_mode_fb_cmd2 cmd = { 0 }; | |
193 | unsigned int bytes_per_pixel; | |
194 | struct drm_framebuffer *fb; | |
195 | unsigned long offset; | |
196 | struct fb_info *info; | |
197 | struct tegra_bo *bo; | |
198 | size_t size; | |
199 | int err; | |
200 | ||
201 | bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); | |
202 | ||
203 | cmd.width = sizes->surface_width; | |
204 | cmd.height = sizes->surface_height; | |
205 | cmd.pitches[0] = sizes->surface_width * bytes_per_pixel; | |
206 | cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, | |
207 | sizes->surface_depth); | |
208 | ||
209 | size = cmd.pitches[0] * cmd.height; | |
210 | ||
773af77f | 211 | bo = tegra_bo_create(drm, size, 0); |
de2ba664 AM |
212 | if (IS_ERR(bo)) |
213 | return PTR_ERR(bo); | |
214 | ||
215 | info = framebuffer_alloc(0, drm->dev); | |
216 | if (!info) { | |
217 | dev_err(drm->dev, "failed to allocate framebuffer info\n"); | |
218 | tegra_bo_free_object(&bo->gem); | |
219 | return -ENOMEM; | |
220 | } | |
221 | ||
222 | fbdev->fb = tegra_fb_alloc(drm, &cmd, &bo, 1); | |
223 | if (IS_ERR(fbdev->fb)) { | |
224 | dev_err(drm->dev, "failed to allocate DRM framebuffer\n"); | |
225 | err = PTR_ERR(fbdev->fb); | |
226 | goto release; | |
227 | } | |
228 | ||
229 | fb = &fbdev->fb->base; | |
230 | helper->fb = fb; | |
231 | helper->fbdev = info; | |
232 | ||
233 | info->par = helper; | |
234 | info->flags = FBINFO_FLAG_DEFAULT; | |
235 | info->fbops = &tegra_fb_ops; | |
236 | ||
237 | err = fb_alloc_cmap(&info->cmap, 256, 0); | |
238 | if (err < 0) { | |
239 | dev_err(drm->dev, "failed to allocate color map: %d\n", err); | |
240 | goto destroy; | |
241 | } | |
242 | ||
243 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); | |
244 | drm_fb_helper_fill_var(info, helper, fb->width, fb->height); | |
245 | ||
246 | offset = info->var.xoffset * bytes_per_pixel + | |
247 | info->var.yoffset * fb->pitches[0]; | |
248 | ||
249 | drm->mode_config.fb_base = (resource_size_t)bo->paddr; | |
9ab34151 | 250 | info->screen_base = (void __iomem *)bo->vaddr + offset; |
de2ba664 AM |
251 | info->screen_size = size; |
252 | info->fix.smem_start = (unsigned long)(bo->paddr + offset); | |
253 | info->fix.smem_len = size; | |
254 | ||
255 | return 0; | |
256 | ||
257 | destroy: | |
258 | drm_framebuffer_unregister_private(fb); | |
259 | tegra_fb_destroy(fb); | |
260 | release: | |
261 | framebuffer_release(info); | |
262 | return err; | |
263 | } | |
264 | ||
265 | static struct drm_fb_helper_funcs tegra_fb_helper_funcs = { | |
266 | .fb_probe = tegra_fbdev_probe, | |
267 | }; | |
268 | ||
269 | static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm, | |
270 | unsigned int preferred_bpp, | |
271 | unsigned int num_crtc, | |
272 | unsigned int max_connectors) | |
273 | { | |
274 | struct drm_fb_helper *helper; | |
275 | struct tegra_fbdev *fbdev; | |
276 | int err; | |
277 | ||
278 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
279 | if (!fbdev) { | |
280 | dev_err(drm->dev, "failed to allocate DRM fbdev\n"); | |
281 | return ERR_PTR(-ENOMEM); | |
282 | } | |
283 | ||
284 | fbdev->base.funcs = &tegra_fb_helper_funcs; | |
285 | helper = &fbdev->base; | |
286 | ||
287 | err = drm_fb_helper_init(drm, &fbdev->base, num_crtc, max_connectors); | |
288 | if (err < 0) { | |
289 | dev_err(drm->dev, "failed to initialize DRM FB helper\n"); | |
290 | goto free; | |
291 | } | |
292 | ||
293 | err = drm_fb_helper_single_add_all_connectors(&fbdev->base); | |
294 | if (err < 0) { | |
295 | dev_err(drm->dev, "failed to add connectors\n"); | |
296 | goto fini; | |
297 | } | |
298 | ||
299 | drm_helper_disable_unused_functions(drm); | |
300 | ||
301 | err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp); | |
302 | if (err < 0) { | |
303 | dev_err(drm->dev, "failed to set initial configuration\n"); | |
304 | goto fini; | |
305 | } | |
306 | ||
307 | return fbdev; | |
308 | ||
309 | fini: | |
310 | drm_fb_helper_fini(&fbdev->base); | |
311 | free: | |
312 | kfree(fbdev); | |
313 | return ERR_PTR(err); | |
314 | } | |
315 | ||
316 | static void tegra_fbdev_free(struct tegra_fbdev *fbdev) | |
317 | { | |
318 | struct fb_info *info = fbdev->base.fbdev; | |
319 | ||
320 | if (info) { | |
321 | int err; | |
322 | ||
323 | err = unregister_framebuffer(info); | |
324 | if (err < 0) | |
325 | DRM_DEBUG_KMS("failed to unregister framebuffer\n"); | |
326 | ||
327 | if (info->cmap.len) | |
328 | fb_dealloc_cmap(&info->cmap); | |
329 | ||
330 | framebuffer_release(info); | |
331 | } | |
332 | ||
333 | if (fbdev->fb) { | |
334 | drm_framebuffer_unregister_private(&fbdev->fb->base); | |
335 | tegra_fb_destroy(&fbdev->fb->base); | |
336 | } | |
337 | ||
338 | drm_fb_helper_fini(&fbdev->base); | |
339 | kfree(fbdev); | |
340 | } | |
341 | ||
342 | static void tegra_fb_output_poll_changed(struct drm_device *drm) | |
d8f4a9ed | 343 | { |
386a2a71 | 344 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 345 | |
386a2a71 TR |
346 | if (tegra->fbdev) |
347 | drm_fb_helper_hotplug_event(&tegra->fbdev->base); | |
d8f4a9ed TR |
348 | } |
349 | ||
350 | static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { | |
de2ba664 AM |
351 | .fb_create = tegra_fb_create, |
352 | .output_poll_changed = tegra_fb_output_poll_changed, | |
d8f4a9ed TR |
353 | }; |
354 | ||
355 | int tegra_drm_fb_init(struct drm_device *drm) | |
356 | { | |
386a2a71 | 357 | struct tegra_drm *tegra = drm->dev_private; |
de2ba664 | 358 | struct tegra_fbdev *fbdev; |
d8f4a9ed TR |
359 | |
360 | drm->mode_config.min_width = 0; | |
361 | drm->mode_config.min_height = 0; | |
362 | ||
363 | drm->mode_config.max_width = 4096; | |
364 | drm->mode_config.max_height = 4096; | |
365 | ||
366 | drm->mode_config.funcs = &tegra_drm_mode_funcs; | |
367 | ||
de2ba664 | 368 | fbdev = tegra_fbdev_create(drm, 32, drm->mode_config.num_crtc, |
d8f4a9ed TR |
369 | drm->mode_config.num_connector); |
370 | if (IS_ERR(fbdev)) | |
371 | return PTR_ERR(fbdev); | |
372 | ||
386a2a71 | 373 | tegra->fbdev = fbdev; |
d8f4a9ed TR |
374 | |
375 | return 0; | |
376 | } | |
377 | ||
378 | void tegra_drm_fb_exit(struct drm_device *drm) | |
379 | { | |
386a2a71 | 380 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 381 | |
386a2a71 | 382 | tegra_fbdev_free(tegra->fbdev); |
de2ba664 AM |
383 | } |
384 | ||
385 | void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) | |
386 | { | |
387 | if (fbdev) { | |
388 | drm_modeset_lock_all(fbdev->base.dev); | |
389 | drm_fb_helper_restore_fbdev_mode(&fbdev->base); | |
390 | drm_modeset_unlock_all(fbdev->base.dev); | |
391 | } | |
d8f4a9ed | 392 | } |