1 | /* |
2 | * Copyright © 2007 David Airlie |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright notice and this permission notice (including the next |
12 | * paragraph) shall be included in all copies or substantial portions of the |
13 | * Software. |
14 | * |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
21 | * DEALINGS IN THE SOFTWARE. |
22 | * |
23 | * Authors: |
24 | * David Airlie |
25 | */ |
26 | #include <linux/module.h> |
27 | #include <linux/slab.h> |
28 | #include <linux/fb.h> |
29 | |
30 | #include <drm/drmP.h> |
31 | #include <drm/drm_crtc.h> |
32 | #include <drm/drm_crtc_helper.h> |
33 | #include <drm/radeon_drm.h> |
34 | #include "radeon.h" |
35 | |
36 | #include <drm/drm_fb_helper.h> |
37 | |
38 | #include <linux/vga_switcheroo.h> |
39 | |
40 | #ifdef __NetBSD__ |
41 | #include "radeondrmkmsfb.h" |
42 | #endif |
43 | |
44 | /* object hierarchy - |
45 | this contains a helper + a radeon fb |
46 | the helper contains a pointer to radeon framebuffer baseclass. |
47 | */ |
48 | struct radeon_fbdev { |
49 | struct drm_fb_helper helper; |
50 | struct radeon_framebuffer rfb; |
51 | struct list_head fbdev_list; |
52 | struct radeon_device *rdev; |
53 | }; |
54 | |
55 | #ifndef __NetBSD__ |
56 | static struct fb_ops radeonfb_ops = { |
57 | .owner = THIS_MODULE, |
58 | .fb_check_var = drm_fb_helper_check_var, |
59 | .fb_set_par = drm_fb_helper_set_par, |
60 | .fb_fillrect = cfb_fillrect, |
61 | .fb_copyarea = cfb_copyarea, |
62 | .fb_imageblit = cfb_imageblit, |
63 | .fb_pan_display = drm_fb_helper_pan_display, |
64 | .fb_blank = drm_fb_helper_blank, |
65 | .fb_setcmap = drm_fb_helper_setcmap, |
66 | .fb_debug_enter = drm_fb_helper_debug_enter, |
67 | .fb_debug_leave = drm_fb_helper_debug_leave, |
68 | }; |
69 | #endif |
70 | |
71 | |
72 | int radeon_align_pitch(struct radeon_device *rdev, int width, int bpp, bool tiled) |
73 | { |
74 | int aligned = width; |
75 | int align_large = (ASIC_IS_AVIVO(rdev)) || tiled; |
76 | int pitch_mask = 0; |
77 | |
78 | switch (bpp / 8) { |
79 | case 1: |
80 | pitch_mask = align_large ? 255 : 127; |
81 | break; |
82 | case 2: |
83 | pitch_mask = align_large ? 127 : 31; |
84 | break; |
85 | case 3: |
86 | case 4: |
87 | pitch_mask = align_large ? 63 : 15; |
88 | break; |
89 | } |
90 | |
91 | aligned += pitch_mask; |
92 | aligned &= ~pitch_mask; |
93 | return aligned; |
94 | } |
95 | |
96 | static void radeonfb_destroy_pinned_object(struct drm_gem_object *gobj) |
97 | { |
98 | struct radeon_bo *rbo = gem_to_radeon_bo(gobj); |
99 | int ret; |
100 | |
101 | ret = radeon_bo_reserve(rbo, false); |
102 | if (likely(ret == 0)) { |
103 | radeon_bo_kunmap(rbo); |
104 | radeon_bo_unpin(rbo); |
105 | radeon_bo_unreserve(rbo); |
106 | } |
107 | drm_gem_object_unreference_unlocked(gobj); |
108 | } |
109 | |
110 | static int radeonfb_create_pinned_object(struct radeon_fbdev *rfbdev, |
111 | struct drm_mode_fb_cmd2 *mode_cmd, |
112 | struct drm_gem_object **gobj_p) |
113 | { |
114 | struct radeon_device *rdev = rfbdev->rdev; |
115 | struct drm_gem_object *gobj = NULL; |
116 | struct radeon_bo *rbo = NULL; |
117 | bool fb_tiled = false; /* useful for testing */ |
118 | u32 tiling_flags = 0; |
119 | int ret; |
120 | int aligned_size, size; |
121 | int height = mode_cmd->height; |
122 | u32 bpp, depth; |
123 | |
124 | drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp); |
125 | |
126 | /* need to align pitch with crtc limits */ |
127 | mode_cmd->pitches[0] = radeon_align_pitch(rdev, mode_cmd->width, bpp, |
128 | fb_tiled) * ((bpp + 1) / 8); |
129 | |
130 | if (rdev->family >= CHIP_R600) |
131 | #ifdef __NetBSD__ /* XXX ALIGN means something else. */ |
132 | height = round_up(mode_cmd->height, 8); |
133 | #else |
134 | height = ALIGN(mode_cmd->height, 8); |
135 | #endif |
136 | size = mode_cmd->pitches[0] * height; |
137 | #ifdef __NetBSD__ /* XXX ALIGN means something else. */ |
138 | aligned_size = round_up(size, PAGE_SIZE); |
139 | #else |
140 | aligned_size = ALIGN(size, PAGE_SIZE); |
141 | #endif |
142 | ret = radeon_gem_object_create(rdev, aligned_size, 0, |
143 | RADEON_GEM_DOMAIN_VRAM, |
144 | false, true, |
145 | &gobj); |
146 | if (ret) { |
147 | printk(KERN_ERR "failed to allocate framebuffer (%d)\n" , |
148 | aligned_size); |
149 | return -ENOMEM; |
150 | } |
151 | rbo = gem_to_radeon_bo(gobj); |
152 | |
153 | if (fb_tiled) |
154 | tiling_flags = RADEON_TILING_MACRO; |
155 | |
156 | #ifdef __BIG_ENDIAN |
157 | switch (bpp) { |
158 | case 32: |
159 | tiling_flags |= RADEON_TILING_SWAP_32BIT; |
160 | break; |
161 | case 16: |
162 | tiling_flags |= RADEON_TILING_SWAP_16BIT; |
163 | default: |
164 | break; |
165 | } |
166 | #endif |
167 | |
168 | if (tiling_flags) { |
169 | ret = radeon_bo_set_tiling_flags(rbo, |
170 | tiling_flags | RADEON_TILING_SURFACE, |
171 | mode_cmd->pitches[0]); |
172 | if (ret) |
173 | dev_err(rdev->dev, "FB failed to set tiling flags\n" ); |
174 | } |
175 | |
176 | |
177 | ret = radeon_bo_reserve(rbo, false); |
178 | if (unlikely(ret != 0)) |
179 | goto out_unref; |
180 | /* Only 27 bit offset for legacy CRTC */ |
181 | ret = radeon_bo_pin_restricted(rbo, RADEON_GEM_DOMAIN_VRAM, |
182 | ASIC_IS_AVIVO(rdev) ? 0 : 1 << 27, |
183 | NULL); |
184 | if (ret) { |
185 | radeon_bo_unreserve(rbo); |
186 | goto out_unref; |
187 | } |
188 | if (fb_tiled) |
189 | radeon_bo_check_tiling(rbo, 0, 0); |
190 | ret = radeon_bo_kmap(rbo, NULL); |
191 | radeon_bo_unreserve(rbo); |
192 | if (ret) { |
193 | goto out_unref; |
194 | } |
195 | |
196 | *gobj_p = gobj; |
197 | return 0; |
198 | out_unref: |
199 | radeonfb_destroy_pinned_object(gobj); |
200 | *gobj_p = NULL; |
201 | return ret; |
202 | } |
203 | |
204 | static int radeonfb_create(struct drm_fb_helper *helper, |
205 | struct drm_fb_helper_surface_size *sizes) |
206 | { |
207 | struct radeon_fbdev *rfbdev = (struct radeon_fbdev *)helper; |
208 | struct radeon_device *rdev = rfbdev->rdev; |
209 | #ifndef __NetBSD__ |
210 | struct fb_info *info; |
211 | #endif |
212 | struct drm_framebuffer *fb = NULL; |
213 | struct drm_mode_fb_cmd2 mode_cmd; |
214 | struct drm_gem_object *gobj = NULL; |
215 | struct radeon_bo *rbo = NULL; |
216 | #ifndef __NetBSD__ |
217 | struct device *device = &rdev->pdev->dev; |
218 | #endif |
219 | int ret; |
220 | #ifndef __NetBSD__ |
221 | unsigned long tmp; |
222 | #endif |
223 | |
224 | mode_cmd.width = sizes->surface_width; |
225 | mode_cmd.height = sizes->surface_height; |
226 | |
227 | /* avivo can't scanout real 24bpp */ |
228 | if ((sizes->surface_bpp == 24) && ASIC_IS_AVIVO(rdev)) |
229 | sizes->surface_bpp = 32; |
230 | |
231 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
232 | sizes->surface_depth); |
233 | |
234 | ret = radeonfb_create_pinned_object(rfbdev, &mode_cmd, &gobj); |
235 | if (ret) { |
236 | DRM_ERROR("failed to create fbcon object %d\n" , ret); |
237 | return ret; |
238 | } |
239 | |
240 | rbo = gem_to_radeon_bo(gobj); |
241 | |
242 | #ifdef __NetBSD__ |
243 | ret = radeon_framebuffer_init(rdev->ddev, &rfbdev->rfb, &mode_cmd, gobj); |
244 | if (ret) { |
245 | DRM_ERROR("failed to initialize framebuffer %d\n" , ret); |
246 | goto out_unref; |
247 | } |
248 | |
249 | (void)memset(rbo->kptr, 0, radeon_bo_size(rbo)); |
250 | |
251 | { |
252 | static const struct radeonfb_attach_args zero_rfa; |
253 | struct radeonfb_attach_args rfa = zero_rfa; |
254 | |
255 | rfa.rfa_fb_helper = helper; |
256 | rfa.rfa_fb_sizes = *sizes; |
257 | rfa.rfa_fb_ptr = rbo->kptr; |
258 | rfa.rfa_fb_linebytes = mode_cmd.pitches[0]; |
259 | |
260 | helper->fbdev = config_found_ia(rdev->ddev->dev, "radeonfbbus" , &rfa, |
261 | NULL); |
262 | if (helper->fbdev == NULL) { |
263 | DRM_ERROR("failed to attach genfb\n" ); |
264 | goto out_unref; |
265 | } |
266 | } |
267 | fb = &rfbdev->rfb.base; |
268 | rfbdev->helper.fb = fb; |
269 | #else |
270 | /* okay we have an object now allocate the framebuffer */ |
271 | info = framebuffer_alloc(0, device); |
272 | if (info == NULL) { |
273 | ret = -ENOMEM; |
274 | goto out_unref; |
275 | } |
276 | |
277 | info->par = rfbdev; |
278 | |
279 | ret = radeon_framebuffer_init(rdev->ddev, &rfbdev->rfb, &mode_cmd, gobj); |
280 | if (ret) { |
281 | DRM_ERROR("failed to initialize framebuffer %d\n" , ret); |
282 | goto out_unref; |
283 | } |
284 | |
285 | fb = &rfbdev->rfb.base; |
286 | |
287 | /* setup helper */ |
288 | rfbdev->helper.fb = fb; |
289 | rfbdev->helper.fbdev = info; |
290 | |
291 | memset_io(rbo->kptr, 0x0, radeon_bo_size(rbo)); |
292 | |
293 | strcpy(info->fix.id, "radeondrmfb" ); |
294 | |
295 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); |
296 | |
297 | info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT; |
298 | info->fbops = &radeonfb_ops; |
299 | |
300 | tmp = radeon_bo_gpu_offset(rbo) - rdev->mc.vram_start; |
301 | info->fix.smem_start = rdev->mc.aper_base + tmp; |
302 | info->fix.smem_len = radeon_bo_size(rbo); |
303 | info->screen_base = rbo->kptr; |
304 | info->screen_size = radeon_bo_size(rbo); |
305 | |
306 | drm_fb_helper_fill_var(info, &rfbdev->helper, sizes->fb_width, sizes->fb_height); |
307 | |
308 | /* setup aperture base/size for vesafb takeover */ |
309 | info->apertures = alloc_apertures(1); |
310 | if (!info->apertures) { |
311 | ret = -ENOMEM; |
312 | goto out_unref; |
313 | } |
314 | info->apertures->ranges[0].base = rdev->ddev->mode_config.fb_base; |
315 | info->apertures->ranges[0].size = rdev->mc.aper_size; |
316 | |
317 | /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ |
318 | |
319 | if (info->screen_base == NULL) { |
320 | ret = -ENOSPC; |
321 | goto out_unref; |
322 | } |
323 | |
324 | ret = fb_alloc_cmap(&info->cmap, 256, 0); |
325 | if (ret) { |
326 | ret = -ENOMEM; |
327 | goto out_unref; |
328 | } |
329 | |
330 | DRM_INFO("fb mappable at 0x%lX\n" , info->fix.smem_start); |
331 | DRM_INFO("vram apper at 0x%lX\n" , (unsigned long)rdev->mc.aper_base); |
332 | DRM_INFO("size %lu\n" , (unsigned long)radeon_bo_size(rbo)); |
333 | DRM_INFO("fb depth is %d\n" , fb->depth); |
334 | DRM_INFO(" pitch is %d\n" , fb->pitches[0]); |
335 | |
336 | vga_switcheroo_client_fb_set(rdev->ddev->pdev, info); |
337 | #endif |
338 | return 0; |
339 | |
340 | out_unref: |
341 | if (rbo) { |
342 | |
343 | } |
344 | if (fb && ret) { |
345 | drm_gem_object_unreference(gobj); |
346 | drm_framebuffer_unregister_private(fb); |
347 | drm_framebuffer_cleanup(fb); |
348 | kfree(fb); |
349 | } |
350 | return ret; |
351 | } |
352 | |
353 | void radeon_fb_output_poll_changed(struct radeon_device *rdev) |
354 | { |
355 | drm_fb_helper_hotplug_event(&rdev->mode_info.rfbdev->helper); |
356 | } |
357 | |
358 | static int radeon_fbdev_destroy(struct drm_device *dev, struct radeon_fbdev *rfbdev) |
359 | { |
360 | #ifndef __NetBSD__ |
361 | struct fb_info *info; |
362 | #endif |
363 | struct radeon_framebuffer *rfb = &rfbdev->rfb; |
364 | #ifdef __NetBSD__ |
365 | int ret; |
366 | #endif |
367 | |
368 | #ifdef __NetBSD__ |
369 | /* XXX errno NetBSD->Linux */ |
370 | ret = -config_detach(rfbdev->helper.fbdev, DETACH_FORCE); |
371 | if (ret) |
372 | DRM_ERROR("failed to detach radeonfb: %d\n" , ret); |
373 | rfbdev->helper.fbdev = NULL; |
374 | #else |
375 | if (rfbdev->helper.fbdev) { |
376 | info = rfbdev->helper.fbdev; |
377 | |
378 | unregister_framebuffer(info); |
379 | if (info->cmap.len) |
380 | fb_dealloc_cmap(&info->cmap); |
381 | framebuffer_release(info); |
382 | } |
383 | #endif |
384 | |
385 | if (rfb->obj) { |
386 | radeonfb_destroy_pinned_object(rfb->obj); |
387 | rfb->obj = NULL; |
388 | } |
389 | drm_fb_helper_fini(&rfbdev->helper); |
390 | drm_framebuffer_unregister_private(&rfb->base); |
391 | drm_framebuffer_cleanup(&rfb->base); |
392 | |
393 | return 0; |
394 | } |
395 | |
396 | static struct drm_fb_helper_funcs radeon_fb_helper_funcs = { |
397 | .gamma_set = radeon_crtc_fb_gamma_set, |
398 | .gamma_get = radeon_crtc_fb_gamma_get, |
399 | .fb_probe = radeonfb_create, |
400 | }; |
401 | |
402 | int radeon_fbdev_init(struct radeon_device *rdev) |
403 | { |
404 | struct radeon_fbdev *rfbdev; |
405 | int bpp_sel = 32; |
406 | int ret; |
407 | |
408 | /* select 8 bpp console on RN50 or 16MB cards */ |
409 | if (ASIC_IS_RN50(rdev) || rdev->mc.real_vram_size <= (32*1024*1024)) |
410 | bpp_sel = 8; |
411 | |
412 | rfbdev = kzalloc(sizeof(struct radeon_fbdev), GFP_KERNEL); |
413 | if (!rfbdev) |
414 | return -ENOMEM; |
415 | |
416 | rfbdev->rdev = rdev; |
417 | rdev->mode_info.rfbdev = rfbdev; |
418 | rfbdev->helper.funcs = &radeon_fb_helper_funcs; |
419 | |
420 | ret = drm_fb_helper_init(rdev->ddev, &rfbdev->helper, |
421 | rdev->num_crtc, |
422 | RADEONFB_CONN_LIMIT); |
423 | if (ret) { |
424 | kfree(rfbdev); |
425 | return ret; |
426 | } |
427 | |
428 | drm_fb_helper_single_add_all_connectors(&rfbdev->helper); |
429 | |
430 | /* disable all the possible outputs/crtcs before entering KMS mode */ |
431 | drm_helper_disable_unused_functions(rdev->ddev); |
432 | |
433 | drm_fb_helper_initial_config(&rfbdev->helper, bpp_sel); |
434 | return 0; |
435 | } |
436 | |
437 | void radeon_fbdev_fini(struct radeon_device *rdev) |
438 | { |
439 | if (!rdev->mode_info.rfbdev) |
440 | return; |
441 | |
442 | radeon_fbdev_destroy(rdev->ddev, rdev->mode_info.rfbdev); |
443 | kfree(rdev->mode_info.rfbdev); |
444 | rdev->mode_info.rfbdev = NULL; |
445 | } |
446 | |
447 | void radeon_fbdev_set_suspend(struct radeon_device *rdev, int state) |
448 | { |
449 | #ifndef __NetBSD__ /* XXX radeon fb suspend */ |
450 | fb_set_suspend(rdev->mode_info.rfbdev->helper.fbdev, state); |
451 | #endif |
452 | } |
453 | |
454 | int radeon_fbdev_total_size(struct radeon_device *rdev) |
455 | { |
456 | struct radeon_bo *robj; |
457 | int size = 0; |
458 | |
459 | robj = gem_to_radeon_bo(rdev->mode_info.rfbdev->rfb.obj); |
460 | size += radeon_bo_size(robj); |
461 | return size; |
462 | } |
463 | |
464 | bool radeon_fbdev_robj_is_fb(struct radeon_device *rdev, struct radeon_bo *robj) |
465 | { |
466 | if (robj == gem_to_radeon_bo(rdev->mode_info.rfbdev->rfb.obj)) |
467 | return true; |
468 | return false; |
469 | } |
470 | |