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