exynos_drm_fbdev.c 6.9 KB
Newer Older
1 2 3 4 5 6 7 8
/* exynos_drm_fbdev.c
 *
 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
 * Authors:
 *	Inki Dae <inki.dae@samsung.com>
 *	Joonyoung Shim <jy0922.shim@samsung.com>
 *	Seung-Woo Kim <sw0312.kim@samsung.com>
 *
9 10 11 12
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
13 14
 */

15 16 17 18
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_crtc_helper.h>
19
#include <drm/exynos_drm.h>
20

21 22
#include <linux/console.h>

23 24
#include "exynos_drm_drv.h"
#include "exynos_drm_fb.h"
M
Mark Brown 已提交
25
#include "exynos_drm_fbdev.h"
26
#include "exynos_drm_iommu.h"
27 28 29 30 31 32 33 34

#define MAX_CONNECTOR		4
#define PREFERRED_BPP		32

#define to_exynos_fbdev(x)	container_of(x, struct exynos_drm_fbdev,\
				drm_fb_helper)

struct exynos_drm_fbdev {
35 36
	struct drm_fb_helper	drm_fb_helper;
	struct exynos_drm_gem	*exynos_gem;
37 38
};

39 40 41 42 43
static int exynos_drm_fb_mmap(struct fb_info *info,
			struct vm_area_struct *vma)
{
	struct drm_fb_helper *helper = info->par;
	struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(helper);
44
	struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
45 46 47 48 49 50 51
	unsigned long vm_size;
	int ret;

	vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;

	vm_size = vma->vm_end - vma->vm_start;

52
	if (vm_size > exynos_gem->size)
53 54
		return -EINVAL;

55
	ret = dma_mmap_attrs(to_dma_dev(helper->dev), vma, exynos_gem->cookie,
56
			     exynos_gem->dma_addr, exynos_gem->size,
57
			     exynos_gem->dma_attrs);
58 59 60 61 62 63 64 65
	if (ret < 0) {
		DRM_ERROR("failed to mmap.\n");
		return ret;
	}

	return 0;
}

66 67
static struct fb_ops exynos_drm_fb_ops = {
	.owner		= THIS_MODULE,
68
	DRM_FB_HELPER_DEFAULT_OPS,
69
	.fb_mmap        = exynos_drm_fb_mmap,
70 71 72
	.fb_fillrect	= drm_fb_helper_cfb_fillrect,
	.fb_copyarea	= drm_fb_helper_cfb_copyarea,
	.fb_imageblit	= drm_fb_helper_cfb_imageblit,
73 74
};

75
static int exynos_drm_fbdev_update(struct drm_fb_helper *helper,
76
				   struct drm_fb_helper_surface_size *sizes,
77
				   struct exynos_drm_gem *exynos_gem)
78
{
79
	struct fb_info *fbi;
80
	struct drm_framebuffer *fb = helper->fb;
V
Ville Syrjälä 已提交
81
	unsigned int size = fb->width * fb->height * fb->format->cpp[0];
82
	unsigned int nr_pages;
83
	unsigned long offset;
84

85 86 87 88 89 90 91 92 93 94
	fbi = drm_fb_helper_alloc_fbi(helper);
	if (IS_ERR(fbi)) {
		DRM_ERROR("failed to allocate fb info.\n");
		return PTR_ERR(fbi);
	}

	fbi->par = helper;
	fbi->flags = FBINFO_FLAG_DEFAULT;
	fbi->fbops = &exynos_drm_fb_ops;

V
Ville Syrjälä 已提交
95
	drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth);
96
	drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
97

98
	nr_pages = exynos_gem->size >> PAGE_SHIFT;
99

100 101 102
	exynos_gem->kvaddr = (void __iomem *) vmap(exynos_gem->pages, nr_pages,
				VM_MAP, pgprot_writecombine(PAGE_KERNEL));
	if (!exynos_gem->kvaddr) {
103 104
		DRM_ERROR("failed to map pages to kernel space.\n");
		return -EIO;
105 106
	}

V
Ville Syrjälä 已提交
107
	offset = fbi->var.xoffset * fb->format->cpp[0];
108
	offset += fbi->var.yoffset * fb->pitches[0];
109

110
	fbi->screen_base = exynos_gem->kvaddr + offset;
111
	fbi->screen_size = size;
112
	fbi->fix.smem_len = size;
113 114

	return 0;
115 116 117 118 119 120
}

static int exynos_drm_fbdev_create(struct drm_fb_helper *helper,
				    struct drm_fb_helper_surface_size *sizes)
{
	struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper);
121
	struct exynos_drm_gem *exynos_gem;
122
	struct drm_device *dev = helper->dev;
123
	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
124
	unsigned long size;
125 126 127 128 129 130 131 132
	int ret;

	DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d\n",
			sizes->surface_width, sizes->surface_height,
			sizes->surface_bpp);

	mode_cmd.width = sizes->surface_width;
	mode_cmd.height = sizes->surface_height;
133 134 135
	mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3);
	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
							  sizes->surface_depth);
136

137
	size = mode_cmd.pitches[0] * mode_cmd.height;
138

139
	exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_CONTIG, size);
140 141 142 143 144
	/*
	 * If physically contiguous memory allocation fails and if IOMMU is
	 * supported then try to get buffer from non physically contiguous
	 * memory area.
	 */
145
	if (IS_ERR(exynos_gem) && is_drm_iommu_supported(dev)) {
146
		dev_warn(dev->dev, "contiguous FB allocation failed, falling back to non-contiguous\n");
147 148
		exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_NONCONTIG,
						   size);
149 150
	}

151 152
	if (IS_ERR(exynos_gem))
		return PTR_ERR(exynos_gem);
153

154
	exynos_fbdev->exynos_gem = exynos_gem;
155

156 157
	helper->fb =
		exynos_drm_framebuffer_init(dev, &mode_cmd, &exynos_gem, 1);
158
	if (IS_ERR(helper->fb)) {
159
		DRM_ERROR("failed to create drm framebuffer.\n");
160
		ret = PTR_ERR(helper->fb);
161
		goto err_destroy_gem;
162 163
	}

164
	ret = exynos_drm_fbdev_update(helper, sizes, exynos_gem);
165
	if (ret < 0)
166
		goto err_destroy_framebuffer;
167 168 169 170 171 172

	return ret;

err_destroy_framebuffer:
	drm_framebuffer_cleanup(helper->fb);
err_destroy_gem:
173
	exynos_drm_gem_destroy(exynos_gem);
174

175 176 177 178 179 180
	/*
	 * if failed, all resources allocated above would be released by
	 * drm_mode_config_cleanup() when drm_load() had been called prior
	 * to any specific driver such as fimd or hdmi driver.
	 */

181 182 183
	return ret;
}

184
static const struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = {
185
	.fb_probe =	exynos_drm_fbdev_create,
186 187 188 189 190 191 192 193 194
};

int exynos_drm_fbdev_init(struct drm_device *dev)
{
	struct exynos_drm_fbdev *fbdev;
	struct exynos_drm_private *private = dev->dev_private;
	struct drm_fb_helper *helper;
	int ret;

195
	if (!dev->mode_config.num_crtc)
196 197 198
		return 0;

	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
199
	if (!fbdev)
200 201 202
		return -ENOMEM;

	private->fb_helper = helper = &fbdev->drm_fb_helper;
203 204

	drm_fb_helper_prepare(dev, helper, &exynos_drm_fb_helper_funcs);
205

206
	ret = drm_fb_helper_init(dev, helper, MAX_CONNECTOR);
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
	if (ret < 0) {
		DRM_ERROR("failed to initialize drm fb helper.\n");
		goto err_init;
	}

	ret = drm_fb_helper_single_add_all_connectors(helper);
	if (ret < 0) {
		DRM_ERROR("failed to register drm_fb_helper_connector.\n");
		goto err_setup;

	}

	ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
	if (ret < 0) {
		DRM_ERROR("failed to set up hw configuration.\n");
		goto err_setup;
	}

	return 0;

err_setup:
	drm_fb_helper_fini(helper);

err_init:
	private->fb_helper = NULL;
	kfree(fbdev);

	return ret;
}

static void exynos_drm_fbdev_destroy(struct drm_device *dev,
				      struct drm_fb_helper *fb_helper)
{
240
	struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(fb_helper);
241
	struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
242 243
	struct drm_framebuffer *fb;

244
	vunmap(exynos_gem->kvaddr);
245

246 247 248
	/* release drm framebuffer and real buffer */
	if (fb_helper->fb && fb_helper->fb->funcs) {
		fb = fb_helper->fb;
249
		if (fb)
R
Rob Clark 已提交
250
			drm_framebuffer_remove(fb);
251 252
	}

253
	drm_fb_helper_unregister_fbi(fb_helper);
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

	drm_fb_helper_fini(fb_helper);
}

void exynos_drm_fbdev_fini(struct drm_device *dev)
{
	struct exynos_drm_private *private = dev->dev_private;
	struct exynos_drm_fbdev *fbdev;

	if (!private || !private->fb_helper)
		return;

	fbdev = to_exynos_fbdev(private->fb_helper);

	exynos_drm_fbdev_destroy(dev, private->fb_helper);
	kfree(fbdev);
	private->fb_helper = NULL;
}