xref: /linux/drivers/gpu/drm/exynos/exynos_drm_fbdev.c (revision 25c6a853fcea78443d6545dbce48b6e899aae85b)
11c248b7dSInki Dae /* exynos_drm_fbdev.c
21c248b7dSInki Dae  *
31c248b7dSInki Dae  * Copyright (c) 2011 Samsung Electronics Co., Ltd.
41c248b7dSInki Dae  * Authors:
51c248b7dSInki Dae  *	Inki Dae <inki.dae@samsung.com>
61c248b7dSInki Dae  *	Joonyoung Shim <jy0922.shim@samsung.com>
71c248b7dSInki Dae  *	Seung-Woo Kim <sw0312.kim@samsung.com>
81c248b7dSInki Dae  *
9d81aecb5SInki Dae  * This program is free software; you can redistribute  it and/or modify it
10d81aecb5SInki Dae  * under  the terms of  the GNU General  Public License as published by the
11d81aecb5SInki Dae  * Free Software Foundation;  either version 2 of the  License, or (at your
12d81aecb5SInki Dae  * option) any later version.
131c248b7dSInki Dae  */
141c248b7dSInki Dae 
15760285e7SDavid Howells #include <drm/drmP.h>
16760285e7SDavid Howells #include <drm/drm_crtc.h>
17760285e7SDavid Howells #include <drm/drm_fb_helper.h>
18760285e7SDavid Howells #include <drm/drm_crtc_helper.h>
19a1bfacf4SVikas Sajjan #include <drm/exynos_drm.h>
201c248b7dSInki Dae 
211c248b7dSInki Dae #include "exynos_drm_drv.h"
221c248b7dSInki Dae #include "exynos_drm_fb.h"
23e30655d0SMark Brown #include "exynos_drm_fbdev.h"
24c704f1b4SInki Dae #include "exynos_drm_iommu.h"
251c248b7dSInki Dae 
261c248b7dSInki Dae #define MAX_CONNECTOR		4
271c248b7dSInki Dae #define PREFERRED_BPP		32
281c248b7dSInki Dae 
291c248b7dSInki Dae #define to_exynos_fbdev(x)	container_of(x, struct exynos_drm_fbdev,\
301c248b7dSInki Dae 				drm_fb_helper)
311c248b7dSInki Dae 
321c248b7dSInki Dae struct exynos_drm_fbdev {
331c248b7dSInki Dae 	struct drm_fb_helper	drm_fb_helper;
34813fd67bSJoonyoung Shim 	struct exynos_drm_gem	*exynos_gem;
351c248b7dSInki Dae };
361c248b7dSInki Dae 
37dd265850SPrathyush K static int exynos_drm_fb_mmap(struct fb_info *info,
38dd265850SPrathyush K 			struct vm_area_struct *vma)
39dd265850SPrathyush K {
40dd265850SPrathyush K 	struct drm_fb_helper *helper = info->par;
41dd265850SPrathyush K 	struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(helper);
42813fd67bSJoonyoung Shim 	struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
43dd265850SPrathyush K 	unsigned long vm_size;
44dd265850SPrathyush K 	int ret;
45dd265850SPrathyush K 
46dd265850SPrathyush K 	vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
47dd265850SPrathyush K 
48dd265850SPrathyush K 	vm_size = vma->vm_end - vma->vm_start;
49dd265850SPrathyush K 
50813fd67bSJoonyoung Shim 	if (vm_size > exynos_gem->size)
51dd265850SPrathyush K 		return -EINVAL;
52dd265850SPrathyush K 
53f43c3596SMarek Szyprowski 	ret = dma_mmap_attrs(to_dma_dev(helper->dev), vma, exynos_gem->cookie,
54813fd67bSJoonyoung Shim 			     exynos_gem->dma_addr, exynos_gem->size,
55813fd67bSJoonyoung Shim 			     &exynos_gem->dma_attrs);
56dd265850SPrathyush K 	if (ret < 0) {
57dd265850SPrathyush K 		DRM_ERROR("failed to mmap.\n");
58dd265850SPrathyush K 		return ret;
59dd265850SPrathyush K 	}
60dd265850SPrathyush K 
61dd265850SPrathyush K 	return 0;
62dd265850SPrathyush K }
63dd265850SPrathyush K 
641c248b7dSInki Dae static struct fb_ops exynos_drm_fb_ops = {
651c248b7dSInki Dae 	.owner		= THIS_MODULE,
66dd265850SPrathyush K 	.fb_mmap        = exynos_drm_fb_mmap,
677c7d4507SArchit Taneja 	.fb_fillrect	= drm_fb_helper_cfb_fillrect,
687c7d4507SArchit Taneja 	.fb_copyarea	= drm_fb_helper_cfb_copyarea,
697c7d4507SArchit Taneja 	.fb_imageblit	= drm_fb_helper_cfb_imageblit,
701c248b7dSInki Dae 	.fb_check_var	= drm_fb_helper_check_var,
7183b316fdSSascha Hauer 	.fb_set_par	= drm_fb_helper_set_par,
721c248b7dSInki Dae 	.fb_blank	= drm_fb_helper_blank,
731c248b7dSInki Dae 	.fb_pan_display	= drm_fb_helper_pan_display,
741c248b7dSInki Dae 	.fb_setcmap	= drm_fb_helper_setcmap,
751c248b7dSInki Dae };
761c248b7dSInki Dae 
7719c8b834SInki Dae static int exynos_drm_fbdev_update(struct drm_fb_helper *helper,
78ecbf1d5aSRob Clark 				   struct drm_fb_helper_surface_size *sizes,
79813fd67bSJoonyoung Shim 				   struct exynos_drm_gem *exynos_gem)
801c248b7dSInki Dae {
81ee885ca5SJoonyoung Shim 	struct fb_info *fbi;
82d7619960SJoonyoung Shim 	struct drm_framebuffer *fb = helper->fb;
83aa6b2b6cSSeung-Woo Kim 	unsigned int size = fb->width * fb->height * (fb->bits_per_pixel >> 3);
84a5d7ac30SCarlo Caione 	unsigned int nr_pages;
8519c8b834SInki Dae 	unsigned long offset;
861c248b7dSInki Dae 
87ee885ca5SJoonyoung Shim 	fbi = drm_fb_helper_alloc_fbi(helper);
88ee885ca5SJoonyoung Shim 	if (IS_ERR(fbi)) {
89ee885ca5SJoonyoung Shim 		DRM_ERROR("failed to allocate fb info.\n");
90ee885ca5SJoonyoung Shim 		return PTR_ERR(fbi);
91ee885ca5SJoonyoung Shim 	}
92ee885ca5SJoonyoung Shim 
93ee885ca5SJoonyoung Shim 	fbi->par = helper;
94ee885ca5SJoonyoung Shim 	fbi->flags = FBINFO_FLAG_DEFAULT;
95ee885ca5SJoonyoung Shim 	fbi->fbops = &exynos_drm_fb_ops;
96ee885ca5SJoonyoung Shim 
9701f2c773SVille Syrjälä 	drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
98ecbf1d5aSRob Clark 	drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
991c248b7dSInki Dae 
100813fd67bSJoonyoung Shim 	nr_pages = exynos_gem->size >> PAGE_SHIFT;
101c704f1b4SInki Dae 
102813fd67bSJoonyoung Shim 	exynos_gem->kvaddr = (void __iomem *) vmap(exynos_gem->pages, nr_pages,
103813fd67bSJoonyoung Shim 				VM_MAP, pgprot_writecombine(PAGE_KERNEL));
104813fd67bSJoonyoung Shim 	if (!exynos_gem->kvaddr) {
1054744ad24SInki Dae 		DRM_ERROR("failed to map pages to kernel space.\n");
106ee885ca5SJoonyoung Shim 		drm_fb_helper_release_fbi(helper);
1074744ad24SInki Dae 		return -EIO;
1084744ad24SInki Dae 	}
1094744ad24SInki Dae 
11019c8b834SInki Dae 	offset = fbi->var.xoffset * (fb->bits_per_pixel >> 3);
11101f2c773SVille Syrjälä 	offset += fbi->var.yoffset * fb->pitches[0];
1121c248b7dSInki Dae 
113813fd67bSJoonyoung Shim 	fbi->screen_base = exynos_gem->kvaddr + offset;
1141c248b7dSInki Dae 	fbi->screen_size = size;
11571b1f195SDaniel Kurtz 	fbi->fix.smem_len = size;
11619c8b834SInki Dae 
11719c8b834SInki Dae 	return 0;
1181c248b7dSInki Dae }
1191c248b7dSInki Dae 
1201c248b7dSInki Dae static int exynos_drm_fbdev_create(struct drm_fb_helper *helper,
1211c248b7dSInki Dae 				    struct drm_fb_helper_surface_size *sizes)
1221c248b7dSInki Dae {
1231c248b7dSInki Dae 	struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper);
124813fd67bSJoonyoung Shim 	struct exynos_drm_gem *exynos_gem;
1251c248b7dSInki Dae 	struct drm_device *dev = helper->dev;
126a794d57dSJoonyoung Shim 	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
1271c248b7dSInki Dae 	struct platform_device *pdev = dev->platformdev;
128e1533c08SJoonyoung Shim 	unsigned long size;
1291c248b7dSInki Dae 	int ret;
1301c248b7dSInki Dae 
1311c248b7dSInki Dae 	DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d\n",
1321c248b7dSInki Dae 			sizes->surface_width, sizes->surface_height,
1331c248b7dSInki Dae 			sizes->surface_bpp);
1341c248b7dSInki Dae 
1351c248b7dSInki Dae 	mode_cmd.width = sizes->surface_width;
1361c248b7dSInki Dae 	mode_cmd.height = sizes->surface_height;
137a794d57dSJoonyoung Shim 	mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3);
138a794d57dSJoonyoung Shim 	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
139a794d57dSJoonyoung Shim 							  sizes->surface_depth);
1401c248b7dSInki Dae 
141e1533c08SJoonyoung Shim 	size = mode_cmd.pitches[0] * mode_cmd.height;
1422b35892eSInki Dae 
143813fd67bSJoonyoung Shim 	exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_CONTIG, size);
144a1bfacf4SVikas Sajjan 	/*
145a1bfacf4SVikas Sajjan 	 * If physically contiguous memory allocation fails and if IOMMU is
146a1bfacf4SVikas Sajjan 	 * supported then try to get buffer from non physically contiguous
147a1bfacf4SVikas Sajjan 	 * memory area.
148a1bfacf4SVikas Sajjan 	 */
149813fd67bSJoonyoung Shim 	if (IS_ERR(exynos_gem) && is_drm_iommu_supported(dev)) {
150a1bfacf4SVikas Sajjan 		dev_warn(&pdev->dev, "contiguous FB allocation failed, falling back to non-contiguous\n");
151813fd67bSJoonyoung Shim 		exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_NONCONTIG,
152813fd67bSJoonyoung Shim 						   size);
153a1bfacf4SVikas Sajjan 	}
154a1bfacf4SVikas Sajjan 
1552f424200SDaniel Vetter 	if (IS_ERR(exynos_gem))
1562f424200SDaniel Vetter 		return PTR_ERR(exynos_gem);
1571c248b7dSInki Dae 
158813fd67bSJoonyoung Shim 	exynos_fbdev->exynos_gem = exynos_gem;
159e1533c08SJoonyoung Shim 
160813fd67bSJoonyoung Shim 	helper->fb =
161813fd67bSJoonyoung Shim 		exynos_drm_framebuffer_init(dev, &mode_cmd, &exynos_gem, 1);
16241eab402SSachin Kamat 	if (IS_ERR(helper->fb)) {
163e1533c08SJoonyoung Shim 		DRM_ERROR("failed to create drm framebuffer.\n");
164e1533c08SJoonyoung Shim 		ret = PTR_ERR(helper->fb);
165662aa6d7SInki Dae 		goto err_destroy_gem;
166e1533c08SJoonyoung Shim 	}
167e1533c08SJoonyoung Shim 
168813fd67bSJoonyoung Shim 	ret = exynos_drm_fbdev_update(helper, sizes, exynos_gem);
169662aa6d7SInki Dae 	if (ret < 0)
1707c7d4507SArchit Taneja 		goto err_destroy_framebuffer;
171662aa6d7SInki Dae 
172662aa6d7SInki Dae 	return ret;
173662aa6d7SInki Dae 
174662aa6d7SInki Dae err_destroy_framebuffer:
175662aa6d7SInki Dae 	drm_framebuffer_cleanup(helper->fb);
176662aa6d7SInki Dae err_destroy_gem:
177813fd67bSJoonyoung Shim 	exynos_drm_gem_destroy(exynos_gem);
1781c248b7dSInki Dae 
1791c248b7dSInki Dae 	/*
1801c248b7dSInki Dae 	 * if failed, all resources allocated above would be released by
1811c248b7dSInki Dae 	 * drm_mode_config_cleanup() when drm_load() had been called prior
1821c248b7dSInki Dae 	 * to any specific driver such as fimd or hdmi driver.
1831c248b7dSInki Dae 	 */
1842f424200SDaniel Vetter 
1851c248b7dSInki Dae 	return ret;
1861c248b7dSInki Dae }
1871c248b7dSInki Dae 
1883a493879SThierry Reding static const struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = {
189cd5428a5SDaniel Vetter 	.fb_probe =	exynos_drm_fbdev_create,
1901c248b7dSInki Dae };
1911c248b7dSInki Dae 
1929230ffc4SJingoo Han static bool exynos_drm_fbdev_is_anything_connected(struct drm_device *dev)
1933eb578e2SAndrzej Hajda {
1943eb578e2SAndrzej Hajda 	struct drm_connector *connector;
1953eb578e2SAndrzej Hajda 	bool ret = false;
1963eb578e2SAndrzej Hajda 
1973eb578e2SAndrzej Hajda 	mutex_lock(&dev->mode_config.mutex);
1983eb578e2SAndrzej Hajda 	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
1993eb578e2SAndrzej Hajda 		if (connector->status != connector_status_connected)
2003eb578e2SAndrzej Hajda 			continue;
2013eb578e2SAndrzej Hajda 
2023eb578e2SAndrzej Hajda 		ret = true;
2033eb578e2SAndrzej Hajda 		break;
2043eb578e2SAndrzej Hajda 	}
2053eb578e2SAndrzej Hajda 	mutex_unlock(&dev->mode_config.mutex);
2063eb578e2SAndrzej Hajda 
2073eb578e2SAndrzej Hajda 	return ret;
2083eb578e2SAndrzej Hajda }
2093eb578e2SAndrzej Hajda 
2101c248b7dSInki Dae int exynos_drm_fbdev_init(struct drm_device *dev)
2111c248b7dSInki Dae {
2121c248b7dSInki Dae 	struct exynos_drm_fbdev *fbdev;
2131c248b7dSInki Dae 	struct exynos_drm_private *private = dev->dev_private;
2141c248b7dSInki Dae 	struct drm_fb_helper *helper;
2151c248b7dSInki Dae 	unsigned int num_crtc;
2161c248b7dSInki Dae 	int ret;
2171c248b7dSInki Dae 
2181c248b7dSInki Dae 	if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
2191c248b7dSInki Dae 		return 0;
2201c248b7dSInki Dae 
2213eb578e2SAndrzej Hajda 	if (!exynos_drm_fbdev_is_anything_connected(dev))
2223eb578e2SAndrzej Hajda 		return 0;
2233eb578e2SAndrzej Hajda 
2241c248b7dSInki Dae 	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
22538bb5253SSachin Kamat 	if (!fbdev)
2261c248b7dSInki Dae 		return -ENOMEM;
2271c248b7dSInki Dae 
2281c248b7dSInki Dae 	private->fb_helper = helper = &fbdev->drm_fb_helper;
22910a23102SThierry Reding 
23010a23102SThierry Reding 	drm_fb_helper_prepare(dev, helper, &exynos_drm_fb_helper_funcs);
2311c248b7dSInki Dae 
2321c248b7dSInki Dae 	num_crtc = dev->mode_config.num_crtc;
2331c248b7dSInki Dae 
2341c248b7dSInki Dae 	ret = drm_fb_helper_init(dev, helper, num_crtc, MAX_CONNECTOR);
2351c248b7dSInki Dae 	if (ret < 0) {
2361c248b7dSInki Dae 		DRM_ERROR("failed to initialize drm fb helper.\n");
2371c248b7dSInki Dae 		goto err_init;
2381c248b7dSInki Dae 	}
2391c248b7dSInki Dae 
2401c248b7dSInki Dae 	ret = drm_fb_helper_single_add_all_connectors(helper);
2411c248b7dSInki Dae 	if (ret < 0) {
2421c248b7dSInki Dae 		DRM_ERROR("failed to register drm_fb_helper_connector.\n");
2431c248b7dSInki Dae 		goto err_setup;
2441c248b7dSInki Dae 
2451c248b7dSInki Dae 	}
2461c248b7dSInki Dae 
2471c248b7dSInki Dae 	ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
2481c248b7dSInki Dae 	if (ret < 0) {
2491c248b7dSInki Dae 		DRM_ERROR("failed to set up hw configuration.\n");
2501c248b7dSInki Dae 		goto err_setup;
2511c248b7dSInki Dae 	}
2521c248b7dSInki Dae 
2531c248b7dSInki Dae 	return 0;
2541c248b7dSInki Dae 
2551c248b7dSInki Dae err_setup:
2561c248b7dSInki Dae 	drm_fb_helper_fini(helper);
2571c248b7dSInki Dae 
2581c248b7dSInki Dae err_init:
2591c248b7dSInki Dae 	private->fb_helper = NULL;
2601c248b7dSInki Dae 	kfree(fbdev);
2611c248b7dSInki Dae 
2621c248b7dSInki Dae 	return ret;
2631c248b7dSInki Dae }
2641c248b7dSInki Dae 
2651c248b7dSInki Dae static void exynos_drm_fbdev_destroy(struct drm_device *dev,
2661c248b7dSInki Dae 				      struct drm_fb_helper *fb_helper)
2671c248b7dSInki Dae {
2684744ad24SInki Dae 	struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(fb_helper);
269813fd67bSJoonyoung Shim 	struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
2701c248b7dSInki Dae 	struct drm_framebuffer *fb;
2711c248b7dSInki Dae 
272813fd67bSJoonyoung Shim 	if (exynos_gem->kvaddr)
273813fd67bSJoonyoung Shim 		vunmap(exynos_gem->kvaddr);
2744744ad24SInki Dae 
2751c248b7dSInki Dae 	/* release drm framebuffer and real buffer */
2761c248b7dSInki Dae 	if (fb_helper->fb && fb_helper->fb->funcs) {
2771c248b7dSInki Dae 		fb = fb_helper->fb;
27836206361SDaniel Vetter 		if (fb) {
27936206361SDaniel Vetter 			drm_framebuffer_unregister_private(fb);
280f7eff60eSRob Clark 			drm_framebuffer_remove(fb);
2811c248b7dSInki Dae 		}
28236206361SDaniel Vetter 	}
2831c248b7dSInki Dae 
2847c7d4507SArchit Taneja 	drm_fb_helper_unregister_fbi(fb_helper);
2857c7d4507SArchit Taneja 	drm_fb_helper_release_fbi(fb_helper);
2861c248b7dSInki Dae 
2871c248b7dSInki Dae 	drm_fb_helper_fini(fb_helper);
2881c248b7dSInki Dae }
2891c248b7dSInki Dae 
2901c248b7dSInki Dae void exynos_drm_fbdev_fini(struct drm_device *dev)
2911c248b7dSInki Dae {
2921c248b7dSInki Dae 	struct exynos_drm_private *private = dev->dev_private;
2931c248b7dSInki Dae 	struct exynos_drm_fbdev *fbdev;
2941c248b7dSInki Dae 
2951c248b7dSInki Dae 	if (!private || !private->fb_helper)
2961c248b7dSInki Dae 		return;
2971c248b7dSInki Dae 
2981c248b7dSInki Dae 	fbdev = to_exynos_fbdev(private->fb_helper);
2991c248b7dSInki Dae 
3001c248b7dSInki Dae 	exynos_drm_fbdev_destroy(dev, private->fb_helper);
3011c248b7dSInki Dae 	kfree(fbdev);
3021c248b7dSInki Dae 	private->fb_helper = NULL;
3031c248b7dSInki Dae }
3041c248b7dSInki Dae 
3051c248b7dSInki Dae void exynos_drm_fbdev_restore_mode(struct drm_device *dev)
3061c248b7dSInki Dae {
3071c248b7dSInki Dae 	struct exynos_drm_private *private = dev->dev_private;
3081c248b7dSInki Dae 
3091c248b7dSInki Dae 	if (!private || !private->fb_helper)
3101c248b7dSInki Dae 		return;
3111c248b7dSInki Dae 
3125ea1f752SRob Clark 	drm_fb_helper_restore_fbdev_mode_unlocked(private->fb_helper);
3131c248b7dSInki Dae }
314*25c6a853SAndrzej Hajda 
315*25c6a853SAndrzej Hajda void exynos_drm_output_poll_changed(struct drm_device *dev)
316*25c6a853SAndrzej Hajda {
317*25c6a853SAndrzej Hajda 	struct exynos_drm_private *private = dev->dev_private;
318*25c6a853SAndrzej Hajda 	struct drm_fb_helper *fb_helper = private->fb_helper;
319*25c6a853SAndrzej Hajda 
320*25c6a853SAndrzej Hajda 	if (fb_helper)
321*25c6a853SAndrzej Hajda 		drm_fb_helper_hotplug_event(fb_helper);
322*25c6a853SAndrzej Hajda 	else
323*25c6a853SAndrzej Hajda 		exynos_drm_fbdev_init(dev);
324*25c6a853SAndrzej Hajda }
325