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 | ||
270 | static struct drm_fb_helper_funcs tegra_fb_helper_funcs = { | |
271 | .fb_probe = tegra_fbdev_probe, | |
272 | }; | |
273 | ||
274 | static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm, | |
275 | unsigned int preferred_bpp, | |
276 | unsigned int num_crtc, | |
277 | unsigned int max_connectors) | |
278 | { | |
279 | struct drm_fb_helper *helper; | |
280 | struct tegra_fbdev *fbdev; | |
281 | int err; | |
282 | ||
283 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
284 | if (!fbdev) { | |
285 | dev_err(drm->dev, "failed to allocate DRM fbdev\n"); | |
286 | return ERR_PTR(-ENOMEM); | |
287 | } | |
288 | ||
289 | fbdev->base.funcs = &tegra_fb_helper_funcs; | |
290 | helper = &fbdev->base; | |
291 | ||
292 | err = drm_fb_helper_init(drm, &fbdev->base, num_crtc, max_connectors); | |
293 | if (err < 0) { | |
294 | dev_err(drm->dev, "failed to initialize DRM FB helper\n"); | |
295 | goto free; | |
296 | } | |
297 | ||
298 | err = drm_fb_helper_single_add_all_connectors(&fbdev->base); | |
299 | if (err < 0) { | |
300 | dev_err(drm->dev, "failed to add connectors\n"); | |
301 | goto fini; | |
302 | } | |
303 | ||
304 | drm_helper_disable_unused_functions(drm); | |
305 | ||
306 | err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp); | |
307 | if (err < 0) { | |
308 | dev_err(drm->dev, "failed to set initial configuration\n"); | |
309 | goto fini; | |
310 | } | |
311 | ||
312 | return fbdev; | |
313 | ||
314 | fini: | |
315 | drm_fb_helper_fini(&fbdev->base); | |
316 | free: | |
317 | kfree(fbdev); | |
318 | return ERR_PTR(err); | |
319 | } | |
320 | ||
321 | static void tegra_fbdev_free(struct tegra_fbdev *fbdev) | |
322 | { | |
323 | struct fb_info *info = fbdev->base.fbdev; | |
324 | ||
325 | if (info) { | |
326 | int err; | |
327 | ||
328 | err = unregister_framebuffer(info); | |
329 | if (err < 0) | |
330 | DRM_DEBUG_KMS("failed to unregister framebuffer\n"); | |
331 | ||
332 | if (info->cmap.len) | |
333 | fb_dealloc_cmap(&info->cmap); | |
334 | ||
335 | framebuffer_release(info); | |
336 | } | |
337 | ||
338 | if (fbdev->fb) { | |
339 | drm_framebuffer_unregister_private(&fbdev->fb->base); | |
340 | tegra_fb_destroy(&fbdev->fb->base); | |
341 | } | |
342 | ||
343 | drm_fb_helper_fini(&fbdev->base); | |
344 | kfree(fbdev); | |
345 | } | |
346 | ||
60c2f709 TR |
347 | void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) |
348 | { | |
349 | if (fbdev) { | |
350 | drm_modeset_lock_all(fbdev->base.dev); | |
351 | drm_fb_helper_restore_fbdev_mode(&fbdev->base); | |
352 | drm_modeset_unlock_all(fbdev->base.dev); | |
353 | } | |
354 | } | |
355 | ||
de2ba664 | 356 | static void tegra_fb_output_poll_changed(struct drm_device *drm) |
d8f4a9ed | 357 | { |
386a2a71 | 358 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 359 | |
386a2a71 TR |
360 | if (tegra->fbdev) |
361 | drm_fb_helper_hotplug_event(&tegra->fbdev->base); | |
d8f4a9ed | 362 | } |
60c2f709 | 363 | #endif |
d8f4a9ed TR |
364 | |
365 | static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { | |
de2ba664 | 366 | .fb_create = tegra_fb_create, |
60c2f709 | 367 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
de2ba664 | 368 | .output_poll_changed = tegra_fb_output_poll_changed, |
60c2f709 | 369 | #endif |
d8f4a9ed TR |
370 | }; |
371 | ||
372 | int tegra_drm_fb_init(struct drm_device *drm) | |
373 | { | |
60c2f709 | 374 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
386a2a71 | 375 | struct tegra_drm *tegra = drm->dev_private; |
60c2f709 | 376 | #endif |
d8f4a9ed TR |
377 | |
378 | drm->mode_config.min_width = 0; | |
379 | drm->mode_config.min_height = 0; | |
380 | ||
381 | drm->mode_config.max_width = 4096; | |
382 | drm->mode_config.max_height = 4096; | |
383 | ||
384 | drm->mode_config.funcs = &tegra_drm_mode_funcs; | |
385 | ||
60c2f709 TR |
386 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
387 | tegra->fbdev = tegra_fbdev_create(drm, 32, drm->mode_config.num_crtc, | |
388 | drm->mode_config.num_connector); | |
389 | if (IS_ERR(tegra->fbdev)) | |
390 | return PTR_ERR(tegra->fbdev); | |
391 | #endif | |
d8f4a9ed TR |
392 | |
393 | return 0; | |
394 | } | |
395 | ||
396 | void tegra_drm_fb_exit(struct drm_device *drm) | |
397 | { | |
60c2f709 | 398 | #ifdef CONFIG_DRM_TEGRA_FBDEV |
386a2a71 | 399 | struct tegra_drm *tegra = drm->dev_private; |
d8f4a9ed | 400 | |
386a2a71 | 401 | tegra_fbdev_free(tegra->fbdev); |
60c2f709 | 402 | #endif |
d8f4a9ed | 403 | } |