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