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