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