1254885Sdumbbell/*
2254885Sdumbbell * Copyright �� 2007 David Airlie
3254885Sdumbbell *
4254885Sdumbbell * Permission is hereby granted, free of charge, to any person obtaining a
5254885Sdumbbell * copy of this software and associated documentation files (the "Software"),
6254885Sdumbbell * to deal in the Software without restriction, including without limitation
7254885Sdumbbell * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8254885Sdumbbell * and/or sell copies of the Software, and to permit persons to whom the
9254885Sdumbbell * Software is furnished to do so, subject to the following conditions:
10254885Sdumbbell *
11254885Sdumbbell * The above copyright notice and this permission notice (including the next
12254885Sdumbbell * paragraph) shall be included in all copies or substantial portions of the
13254885Sdumbbell * Software.
14254885Sdumbbell *
15254885Sdumbbell * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16254885Sdumbbell * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17254885Sdumbbell * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18254885Sdumbbell * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19254885Sdumbbell * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20254885Sdumbbell * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21254885Sdumbbell * DEALINGS IN THE SOFTWARE.
22254885Sdumbbell *
23254885Sdumbbell * Authors:
24254885Sdumbbell *     David Airlie
25254885Sdumbbell */
26254885Sdumbbell
27254885Sdumbbell#include <sys/cdefs.h>
28254885Sdumbbell__FBSDID("$FreeBSD$");
29254885Sdumbbell
30262861Sjhb#include <machine/_inttypes.h>
31262861Sjhb
32254885Sdumbbell#include <dev/drm2/drmP.h>
33254885Sdumbbell#include <dev/drm2/drm_crtc.h>
34254885Sdumbbell#include <dev/drm2/drm_crtc_helper.h>
35254885Sdumbbell#include <dev/drm2/radeon/radeon_drm.h>
36254885Sdumbbell#include "radeon.h"
37254885Sdumbbell
38254885Sdumbbell#include <dev/drm2/drm_fb_helper.h>
39254885Sdumbbell
40254885Sdumbbell/* object hierarchy -
41254885Sdumbbell   this contains a helper + a radeon fb
42254885Sdumbbell   the helper contains a pointer to radeon framebuffer baseclass.
43254885Sdumbbell*/
44254885Sdumbbellstruct radeon_fbdev {
45254885Sdumbbell	struct drm_fb_helper helper;
46254885Sdumbbell	struct radeon_framebuffer rfb;
47254885Sdumbbell	struct list_head fbdev_list;
48254885Sdumbbell	struct radeon_device *rdev;
49254885Sdumbbell};
50254885Sdumbbell
51262861Sjhb#if defined(__linux__)
52254885Sdumbbellstatic struct fb_ops radeonfb_ops = {
53254885Sdumbbell	.owner = THIS_MODULE,
54254885Sdumbbell	.fb_check_var = drm_fb_helper_check_var,
55254885Sdumbbell	.fb_set_par = drm_fb_helper_set_par,
56254885Sdumbbell	.fb_fillrect = cfb_fillrect,
57254885Sdumbbell	.fb_copyarea = cfb_copyarea,
58254885Sdumbbell	.fb_imageblit = cfb_imageblit,
59254885Sdumbbell	.fb_pan_display = drm_fb_helper_pan_display,
60254885Sdumbbell	.fb_blank = drm_fb_helper_blank,
61254885Sdumbbell	.fb_setcmap = drm_fb_helper_setcmap,
62254885Sdumbbell	.fb_debug_enter = drm_fb_helper_debug_enter,
63254885Sdumbbell	.fb_debug_leave = drm_fb_helper_debug_leave,
64254885Sdumbbell};
65262861Sjhb#endif
66254885Sdumbbell
67254885Sdumbbell
68254885Sdumbbellint radeon_align_pitch(struct radeon_device *rdev, int width, int bpp, bool tiled)
69254885Sdumbbell{
70254885Sdumbbell	int aligned = width;
71254885Sdumbbell	int align_large = (ASIC_IS_AVIVO(rdev)) || tiled;
72254885Sdumbbell	int pitch_mask = 0;
73254885Sdumbbell
74254885Sdumbbell	switch (bpp / 8) {
75254885Sdumbbell	case 1:
76254885Sdumbbell		pitch_mask = align_large ? 255 : 127;
77254885Sdumbbell		break;
78254885Sdumbbell	case 2:
79254885Sdumbbell		pitch_mask = align_large ? 127 : 31;
80254885Sdumbbell		break;
81254885Sdumbbell	case 3:
82254885Sdumbbell	case 4:
83254885Sdumbbell		pitch_mask = align_large ? 63 : 15;
84254885Sdumbbell		break;
85254885Sdumbbell	}
86254885Sdumbbell
87254885Sdumbbell	aligned += pitch_mask;
88254885Sdumbbell	aligned &= ~pitch_mask;
89254885Sdumbbell	return aligned;
90254885Sdumbbell}
91254885Sdumbbell
92254885Sdumbbellstatic void radeonfb_destroy_pinned_object(struct drm_gem_object *gobj)
93254885Sdumbbell{
94254885Sdumbbell	struct radeon_bo *rbo = gem_to_radeon_bo(gobj);
95254885Sdumbbell	int ret;
96254885Sdumbbell
97254885Sdumbbell	ret = radeon_bo_reserve(rbo, false);
98254885Sdumbbell	if (likely(ret == 0)) {
99254885Sdumbbell		radeon_bo_kunmap(rbo);
100254885Sdumbbell		radeon_bo_unpin(rbo);
101254885Sdumbbell		radeon_bo_unreserve(rbo);
102254885Sdumbbell	}
103254885Sdumbbell	drm_gem_object_unreference_unlocked(gobj);
104254885Sdumbbell}
105254885Sdumbbell
106254885Sdumbbellstatic int radeonfb_create_pinned_object(struct radeon_fbdev *rfbdev,
107254885Sdumbbell					 struct drm_mode_fb_cmd2 *mode_cmd,
108254885Sdumbbell					 struct drm_gem_object **gobj_p)
109254885Sdumbbell{
110254885Sdumbbell	struct radeon_device *rdev = rfbdev->rdev;
111254885Sdumbbell	struct drm_gem_object *gobj = NULL;
112254885Sdumbbell	struct radeon_bo *rbo = NULL;
113254885Sdumbbell	bool fb_tiled = false; /* useful for testing */
114254885Sdumbbell	u32 tiling_flags = 0;
115254885Sdumbbell	int ret;
116254885Sdumbbell	int aligned_size, size;
117254885Sdumbbell	int height = mode_cmd->height;
118254885Sdumbbell	u32 bpp, depth;
119254885Sdumbbell
120254885Sdumbbell	drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp);
121254885Sdumbbell
122254885Sdumbbell	/* need to align pitch with crtc limits */
123254885Sdumbbell	mode_cmd->pitches[0] = radeon_align_pitch(rdev, mode_cmd->width, bpp,
124254885Sdumbbell						  fb_tiled) * ((bpp + 1) / 8);
125254885Sdumbbell
126254885Sdumbbell	if (rdev->family >= CHIP_R600)
127254885Sdumbbell		height = roundup2(mode_cmd->height, 8);
128254885Sdumbbell	size = mode_cmd->pitches[0] * height;
129254885Sdumbbell	aligned_size = roundup2(size, PAGE_SIZE);
130254885Sdumbbell	ret = radeon_gem_object_create(rdev, aligned_size, 0,
131254885Sdumbbell				       RADEON_GEM_DOMAIN_VRAM,
132254885Sdumbbell				       false, true,
133254885Sdumbbell				       &gobj);
134254885Sdumbbell	if (ret) {
135254885Sdumbbell		DRM_ERROR("failed to allocate framebuffer (%d)\n",
136254885Sdumbbell		       aligned_size);
137254885Sdumbbell		return -ENOMEM;
138254885Sdumbbell	}
139254885Sdumbbell	rbo = gem_to_radeon_bo(gobj);
140254885Sdumbbell
141254885Sdumbbell	if (fb_tiled)
142254885Sdumbbell		tiling_flags = RADEON_TILING_MACRO;
143254885Sdumbbell
144254885Sdumbbell#ifdef __BIG_ENDIAN
145254885Sdumbbell	switch (bpp) {
146254885Sdumbbell	case 32:
147254885Sdumbbell		tiling_flags |= RADEON_TILING_SWAP_32BIT;
148254885Sdumbbell		break;
149254885Sdumbbell	case 16:
150254885Sdumbbell		tiling_flags |= RADEON_TILING_SWAP_16BIT;
151254885Sdumbbell	default:
152254885Sdumbbell		break;
153254885Sdumbbell	}
154254885Sdumbbell#endif
155254885Sdumbbell
156254885Sdumbbell	if (tiling_flags) {
157254885Sdumbbell		ret = radeon_bo_set_tiling_flags(rbo,
158254885Sdumbbell						 tiling_flags | RADEON_TILING_SURFACE,
159254885Sdumbbell						 mode_cmd->pitches[0]);
160254885Sdumbbell		if (ret)
161254885Sdumbbell			dev_err(rdev->dev, "FB failed to set tiling flags\n");
162254885Sdumbbell	}
163254885Sdumbbell
164254885Sdumbbell
165254885Sdumbbell	ret = radeon_bo_reserve(rbo, false);
166254885Sdumbbell	if (unlikely(ret != 0))
167254885Sdumbbell		goto out_unref;
168254885Sdumbbell	/* Only 27 bit offset for legacy CRTC */
169254885Sdumbbell	ret = radeon_bo_pin_restricted(rbo, RADEON_GEM_DOMAIN_VRAM,
170254885Sdumbbell				       ASIC_IS_AVIVO(rdev) ? 0 : 1 << 27,
171254885Sdumbbell				       NULL);
172254885Sdumbbell	if (ret) {
173254885Sdumbbell		radeon_bo_unreserve(rbo);
174254885Sdumbbell		goto out_unref;
175254885Sdumbbell	}
176254885Sdumbbell	if (fb_tiled)
177254885Sdumbbell		radeon_bo_check_tiling(rbo, 0, 0);
178254885Sdumbbell	ret = radeon_bo_kmap(rbo, NULL);
179254885Sdumbbell	radeon_bo_unreserve(rbo);
180254885Sdumbbell	if (ret) {
181254885Sdumbbell		goto out_unref;
182254885Sdumbbell	}
183254885Sdumbbell
184254885Sdumbbell	*gobj_p = gobj;
185254885Sdumbbell	return 0;
186254885Sdumbbellout_unref:
187254885Sdumbbell	radeonfb_destroy_pinned_object(gobj);
188254885Sdumbbell	*gobj_p = NULL;
189254885Sdumbbell	return ret;
190254885Sdumbbell}
191254885Sdumbbell
192254885Sdumbbellstatic int radeonfb_create(struct radeon_fbdev *rfbdev,
193254885Sdumbbell			   struct drm_fb_helper_surface_size *sizes)
194254885Sdumbbell{
195254885Sdumbbell	struct radeon_device *rdev = rfbdev->rdev;
196254885Sdumbbell	struct fb_info *info;
197254885Sdumbbell	struct drm_framebuffer *fb = NULL;
198254885Sdumbbell	struct drm_mode_fb_cmd2 mode_cmd;
199254885Sdumbbell	struct drm_gem_object *gobj = NULL;
200254885Sdumbbell	struct radeon_bo *rbo = NULL;
201254885Sdumbbell	int ret;
202254885Sdumbbell	unsigned long tmp;
203254885Sdumbbell
204254885Sdumbbell	mode_cmd.width = sizes->surface_width;
205254885Sdumbbell	mode_cmd.height = sizes->surface_height;
206254885Sdumbbell
207254885Sdumbbell	/* avivo can't scanout real 24bpp */
208254885Sdumbbell	if ((sizes->surface_bpp == 24) && ASIC_IS_AVIVO(rdev))
209254885Sdumbbell		sizes->surface_bpp = 32;
210254885Sdumbbell
211254885Sdumbbell	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
212254885Sdumbbell							  sizes->surface_depth);
213254885Sdumbbell
214254885Sdumbbell	ret = radeonfb_create_pinned_object(rfbdev, &mode_cmd, &gobj);
215254885Sdumbbell	if (ret) {
216254885Sdumbbell		DRM_ERROR("failed to create fbcon object %d\n", ret);
217254885Sdumbbell		return ret;
218254885Sdumbbell	}
219254885Sdumbbell
220254885Sdumbbell	rbo = gem_to_radeon_bo(gobj);
221254885Sdumbbell
222262861Sjhb	info = malloc(sizeof(*info), DRM_MEM_KMS, M_WAITOK | M_ZERO);
223254885Sdumbbell
224254885Sdumbbell	ret = radeon_framebuffer_init(rdev->ddev, &rfbdev->rfb, &mode_cmd, gobj);
225254885Sdumbbell	if (ret) {
226254885Sdumbbell		DRM_ERROR("failed to initalise framebuffer %d\n", ret);
227254885Sdumbbell		goto out_unref;
228254885Sdumbbell	}
229254885Sdumbbell
230254885Sdumbbell	fb = &rfbdev->rfb.base;
231254885Sdumbbell
232254885Sdumbbell	/* setup helper */
233254885Sdumbbell	rfbdev->helper.fb = fb;
234254885Sdumbbell	rfbdev->helper.fbdev = info;
235254885Sdumbbell
236262861Sjhb	memset(rbo->kptr, 0x0, radeon_bo_size(rbo));
237254885Sdumbbell
238254885Sdumbbell	tmp = radeon_bo_gpu_offset(rbo) - rdev->mc.vram_start;
239262861Sjhb	info->fb_size  = radeon_bo_size(rbo);
240262861Sjhb	info->fb_bpp = sizes->surface_bpp;
241262861Sjhb	info->fb_width = sizes->surface_width;
242262861Sjhb	info->fb_height = sizes->surface_height;
243262861Sjhb	info->fb_pbase = rdev->mc.aper_base + tmp;
244262861Sjhb	info->fb_vbase = (vm_offset_t)rbo->kptr;
245254885Sdumbbell
246262861Sjhb	DRM_INFO("fb mappable at 0x%" PRIXPTR "\n",  info->fb_pbase);
247254885Sdumbbell	DRM_INFO("vram apper at 0x%lX\n",  (unsigned long)rdev->mc.aper_base);
248254885Sdumbbell	DRM_INFO("size %lu\n", (unsigned long)radeon_bo_size(rbo));
249254885Sdumbbell	DRM_INFO("fb depth is %d\n", fb->depth);
250254885Sdumbbell	DRM_INFO("   pitch is %d\n", fb->pitches[0]);
251254885Sdumbbell
252254885Sdumbbell	return 0;
253254885Sdumbbell
254254885Sdumbbellout_unref:
255254885Sdumbbell	if (rbo) {
256262861Sjhb		/* TODO? dumbbell@ */
257254885Sdumbbell	}
258254885Sdumbbell	if (fb && ret) {
259254885Sdumbbell		drm_gem_object_unreference(gobj);
260254885Sdumbbell		drm_framebuffer_cleanup(fb);
261254885Sdumbbell		free(fb, DRM_MEM_DRIVER); /* XXX malloc'd in radeon_user_framebuffer_create? */
262254885Sdumbbell	}
263254885Sdumbbell	return ret;
264254885Sdumbbell}
265254885Sdumbbell
266254885Sdumbbellstatic int radeon_fb_find_or_create_single(struct drm_fb_helper *helper,
267254885Sdumbbell					   struct drm_fb_helper_surface_size *sizes)
268254885Sdumbbell{
269254885Sdumbbell	struct radeon_fbdev *rfbdev = (struct radeon_fbdev *)helper;
270254885Sdumbbell	int new_fb = 0;
271254885Sdumbbell	int ret;
272254885Sdumbbell
273254885Sdumbbell	if (!helper->fb) {
274254885Sdumbbell		ret = radeonfb_create(rfbdev, sizes);
275254885Sdumbbell		if (ret)
276254885Sdumbbell			return ret;
277254885Sdumbbell		new_fb = 1;
278254885Sdumbbell	}
279254885Sdumbbell	return new_fb;
280254885Sdumbbell}
281254885Sdumbbell
282254885Sdumbbellvoid radeon_fb_output_poll_changed(struct radeon_device *rdev)
283254885Sdumbbell{
284254885Sdumbbell	drm_fb_helper_hotplug_event(&rdev->mode_info.rfbdev->helper);
285254885Sdumbbell}
286254885Sdumbbell
287254885Sdumbbellstatic int radeon_fbdev_destroy(struct drm_device *dev, struct radeon_fbdev *rfbdev)
288254885Sdumbbell{
289254885Sdumbbell	struct fb_info *info;
290254885Sdumbbell	struct radeon_framebuffer *rfb = &rfbdev->rfb;
291254885Sdumbbell
292254885Sdumbbell	if (rfbdev->helper.fbdev) {
293254885Sdumbbell		info = rfbdev->helper.fbdev;
294271090Sdumbbell		free(info->fb_priv, DRM_MEM_KMS);
295262861Sjhb		free(info, DRM_MEM_KMS);
296254885Sdumbbell	}
297254885Sdumbbell
298254885Sdumbbell	if (rfb->obj) {
299254885Sdumbbell		DRM_UNLOCK(dev); /* Work around lock recursion. dumbbell@ */
300254885Sdumbbell		radeonfb_destroy_pinned_object(rfb->obj);
301254885Sdumbbell		DRM_LOCK(dev);
302254885Sdumbbell		rfb->obj = NULL;
303254885Sdumbbell	}
304254885Sdumbbell	drm_fb_helper_fini(&rfbdev->helper);
305254885Sdumbbell	drm_framebuffer_cleanup(&rfb->base);
306254885Sdumbbell
307254885Sdumbbell	return 0;
308254885Sdumbbell}
309254885Sdumbbell
310254885Sdumbbellstatic struct drm_fb_helper_funcs radeon_fb_helper_funcs = {
311254885Sdumbbell	.gamma_set = radeon_crtc_fb_gamma_set,
312254885Sdumbbell	.gamma_get = radeon_crtc_fb_gamma_get,
313254885Sdumbbell	.fb_probe = radeon_fb_find_or_create_single,
314254885Sdumbbell};
315254885Sdumbbell
316254885Sdumbbellint radeon_fbdev_init(struct radeon_device *rdev)
317254885Sdumbbell{
318254885Sdumbbell	struct radeon_fbdev *rfbdev;
319254885Sdumbbell	int bpp_sel = 32;
320254885Sdumbbell	int ret;
321254885Sdumbbell
322254885Sdumbbell	/* select 8 bpp console on RN50 or 16MB cards */
323254885Sdumbbell	if (ASIC_IS_RN50(rdev) || rdev->mc.real_vram_size <= (32*1024*1024))
324254885Sdumbbell		bpp_sel = 8;
325254885Sdumbbell
326254885Sdumbbell	rfbdev = malloc(sizeof(struct radeon_fbdev),
327254885Sdumbbell	    DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
328254885Sdumbbell	if (!rfbdev)
329254885Sdumbbell		return -ENOMEM;
330254885Sdumbbell
331254885Sdumbbell	rfbdev->rdev = rdev;
332254885Sdumbbell	rdev->mode_info.rfbdev = rfbdev;
333254885Sdumbbell	rfbdev->helper.funcs = &radeon_fb_helper_funcs;
334254885Sdumbbell
335254885Sdumbbell	ret = drm_fb_helper_init(rdev->ddev, &rfbdev->helper,
336254885Sdumbbell				 rdev->num_crtc,
337254885Sdumbbell				 RADEONFB_CONN_LIMIT);
338254885Sdumbbell	if (ret) {
339254885Sdumbbell		free(rfbdev, DRM_MEM_DRIVER);
340254885Sdumbbell		return ret;
341254885Sdumbbell	}
342254885Sdumbbell
343254885Sdumbbell	drm_fb_helper_single_add_all_connectors(&rfbdev->helper);
344254885Sdumbbell	drm_fb_helper_initial_config(&rfbdev->helper, bpp_sel);
345254885Sdumbbell	return 0;
346254885Sdumbbell}
347254885Sdumbbell
348254885Sdumbbellvoid radeon_fbdev_fini(struct radeon_device *rdev)
349254885Sdumbbell{
350254885Sdumbbell	if (!rdev->mode_info.rfbdev)
351254885Sdumbbell		return;
352254885Sdumbbell
353254885Sdumbbell	radeon_fbdev_destroy(rdev->ddev, rdev->mode_info.rfbdev);
354254885Sdumbbell	free(rdev->mode_info.rfbdev, DRM_MEM_DRIVER);
355254885Sdumbbell	rdev->mode_info.rfbdev = NULL;
356254885Sdumbbell}
357254885Sdumbbell
358254885Sdumbbellvoid radeon_fbdev_set_suspend(struct radeon_device *rdev, int state)
359254885Sdumbbell{
360254885Sdumbbell#ifdef DUMBBELL_WIP
361254885Sdumbbell	fb_set_suspend(rdev->mode_info.rfbdev->helper.fbdev, state);
362254885Sdumbbell#endif /* DUMBBELL_WIP */
363254885Sdumbbell}
364254885Sdumbbell
365254885Sdumbbellint radeon_fbdev_total_size(struct radeon_device *rdev)
366254885Sdumbbell{
367254885Sdumbbell	struct radeon_bo *robj;
368254885Sdumbbell	int size = 0;
369254885Sdumbbell
370254885Sdumbbell	robj = gem_to_radeon_bo(rdev->mode_info.rfbdev->rfb.obj);
371254885Sdumbbell	size += radeon_bo_size(robj);
372254885Sdumbbell	return size;
373254885Sdumbbell}
374254885Sdumbbell
375254885Sdumbbellbool radeon_fbdev_robj_is_fb(struct radeon_device *rdev, struct radeon_bo *robj)
376254885Sdumbbell{
377254885Sdumbbell	if (robj == gem_to_radeon_bo(rdev->mode_info.rfbdev->rfb.obj))
378254885Sdumbbell		return true;
379254885Sdumbbell	return false;
380254885Sdumbbell}
381262861Sjhb
382262861Sjhbstruct fb_info *
383262861Sjhbradeon_fb_helper_getinfo(device_t kdev)
384262861Sjhb{
385262861Sjhb	struct drm_device *dev;
386262861Sjhb	struct radeon_device *rdev;
387262861Sjhb	struct radeon_fbdev *rfbdev;
388262861Sjhb	struct fb_info *info;
389262861Sjhb
390262861Sjhb	dev = device_get_softc(kdev);
391262861Sjhb	rdev = dev->dev_private;
392262861Sjhb	rfbdev = rdev->mode_info.rfbdev;
393262861Sjhb	if (rfbdev == NULL)
394262861Sjhb		return (NULL);
395262861Sjhb
396262861Sjhb	info = rfbdev->helper.fbdev;
397262861Sjhb
398262861Sjhb	return (info);
399262861Sjhb}
400