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