diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/fb.c')
-rw-r--r-- | drivers/gpu/drm/tegra/fb.c | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c new file mode 100644 index 000000000000..490f7719e317 --- /dev/null +++ b/drivers/gpu/drm/tegra/fb.c | |||
@@ -0,0 +1,392 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012-2013 Avionic Design GmbH | ||
3 | * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. | ||
4 | * | ||
5 | * Based on the KMS/FB CMA helpers | ||
6 | * Copyright (C) 2012 Analog Device Inc. | ||
7 | * | ||
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" | ||
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 | |||
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 | |||
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 | |||
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); | ||
171 | |||
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 | |||
211 | bo = tegra_bo_create(drm, size, 0); | ||
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; | ||
250 | info->screen_base = bo->vaddr + offset; | ||
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) | ||
343 | { | ||
344 | struct tegra_drm *tegra = drm->dev_private; | ||
345 | |||
346 | if (tegra->fbdev) | ||
347 | drm_fb_helper_hotplug_event(&tegra->fbdev->base); | ||
348 | } | ||
349 | |||
350 | static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { | ||
351 | .fb_create = tegra_fb_create, | ||
352 | .output_poll_changed = tegra_fb_output_poll_changed, | ||
353 | }; | ||
354 | |||
355 | int tegra_drm_fb_init(struct drm_device *drm) | ||
356 | { | ||
357 | struct tegra_drm *tegra = drm->dev_private; | ||
358 | struct tegra_fbdev *fbdev; | ||
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 | |||
368 | fbdev = tegra_fbdev_create(drm, 32, drm->mode_config.num_crtc, | ||
369 | drm->mode_config.num_connector); | ||
370 | if (IS_ERR(fbdev)) | ||
371 | return PTR_ERR(fbdev); | ||
372 | |||
373 | tegra->fbdev = fbdev; | ||
374 | |||
375 | return 0; | ||
376 | } | ||
377 | |||
378 | void tegra_drm_fb_exit(struct drm_device *drm) | ||
379 | { | ||
380 | struct tegra_drm *tegra = drm->dev_private; | ||
381 | |||
382 | tegra_fbdev_free(tegra->fbdev); | ||
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 | } | ||
392 | } | ||