1caab277bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
28bb0daffSRob Clark /*
31b409fdaSAlexander A. Klimov  * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/
48bb0daffSRob Clark  * Author: Rob Clark <rob@ti.com>
58bb0daffSRob Clark  */
68bb0daffSRob Clark 
795da53d6SThomas Zimmermann #include <linux/fb.h>
895da53d6SThomas Zimmermann 
99e69bcd8SThomas Zimmermann #include <drm/drm_drv.h>
109e69bcd8SThomas Zimmermann #include <drm/drm_crtc_helper.h>
112d278f54SLaurent Pinchart #include <drm/drm_fb_helper.h>
1281f6156cSSam Ravnborg #include <drm/drm_file.h>
1381f6156cSSam Ravnborg #include <drm/drm_fourcc.h>
14720cf96dSVille Syrjälä #include <drm/drm_framebuffer.h>
15194c9e20SThomas Zimmermann #include <drm/drm_gem_framebuffer_helper.h>
169e69bcd8SThomas Zimmermann #include <drm/drm_util.h>
178bb0daffSRob Clark 
182d278f54SLaurent Pinchart #include "omap_drv.h"
198e3aac3bSThomas Zimmermann #include "omap_fbdev.h"
208bb0daffSRob Clark 
218bb0daffSRob Clark MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')");
228bb0daffSRob Clark static bool ywrap_enabled = true;
238bb0daffSRob Clark module_param_named(ywrap, ywrap_enabled, bool, 0644);
248bb0daffSRob Clark 
258bb0daffSRob Clark /*
268bb0daffSRob Clark  * fbdev funcs, to implement legacy fbdev interface on top of drm driver
278bb0daffSRob Clark  */
288bb0daffSRob Clark 
298bb0daffSRob Clark #define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base)
308bb0daffSRob Clark 
318bb0daffSRob Clark struct omap_fbdev {
328bb0daffSRob Clark 	struct drm_fb_helper base;
338bb0daffSRob Clark 	bool ywrap_enabled;
348bb0daffSRob Clark 
358bb0daffSRob Clark 	/* for deferred dmm roll when getting called in atomic ctx */
368bb0daffSRob Clark 	struct work_struct work;
378bb0daffSRob Clark };
388bb0daffSRob Clark 
398bb0daffSRob Clark static struct drm_fb_helper *get_fb(struct fb_info *fbi);
408bb0daffSRob Clark 
pan_worker(struct work_struct * work)418bb0daffSRob Clark static void pan_worker(struct work_struct *work)
428bb0daffSRob Clark {
438bb0daffSRob Clark 	struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work);
44194c9e20SThomas Zimmermann 	struct drm_fb_helper *helper = &fbdev->base;
45194c9e20SThomas Zimmermann 	struct fb_info *fbi = helper->info;
46194c9e20SThomas Zimmermann 	struct drm_gem_object *bo = drm_gem_fb_get_obj(helper->fb, 0);
478bb0daffSRob Clark 	int npages;
488bb0daffSRob Clark 
498bb0daffSRob Clark 	/* DMM roll shifts in 4K pages: */
508bb0daffSRob Clark 	npages = fbi->fix.line_length >> PAGE_SHIFT;
51194c9e20SThomas Zimmermann 	omap_gem_roll(bo, fbi->var.yoffset * npages);
528bb0daffSRob Clark }
538bb0daffSRob Clark 
omap_fbdev_pan_display(struct fb_var_screeninfo * var,struct fb_info * fbi)548bb0daffSRob Clark static int omap_fbdev_pan_display(struct fb_var_screeninfo *var,
558bb0daffSRob Clark 		struct fb_info *fbi)
568bb0daffSRob Clark {
578bb0daffSRob Clark 	struct drm_fb_helper *helper = get_fb(fbi);
588bb0daffSRob Clark 	struct omap_fbdev *fbdev = to_omap_fbdev(helper);
598bb0daffSRob Clark 
608bb0daffSRob Clark 	if (!helper)
618bb0daffSRob Clark 		goto fallback;
628bb0daffSRob Clark 
638bb0daffSRob Clark 	if (!fbdev->ywrap_enabled)
648bb0daffSRob Clark 		goto fallback;
658bb0daffSRob Clark 
668bb0daffSRob Clark 	if (drm_can_sleep()) {
678bb0daffSRob Clark 		pan_worker(&fbdev->work);
688bb0daffSRob Clark 	} else {
698bb0daffSRob Clark 		struct omap_drm_private *priv = helper->dev->dev_private;
708bb0daffSRob Clark 		queue_work(priv->wq, &fbdev->work);
718bb0daffSRob Clark 	}
728bb0daffSRob Clark 
738bb0daffSRob Clark 	return 0;
748bb0daffSRob Clark 
758bb0daffSRob Clark fallback:
768bb0daffSRob Clark 	return drm_fb_helper_pan_display(var, fbi);
778bb0daffSRob Clark }
788bb0daffSRob Clark 
omap_fbdev_fb_mmap(struct fb_info * info,struct vm_area_struct * vma)79da6eb399SThomas Zimmermann static int omap_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
80da6eb399SThomas Zimmermann {
81da6eb399SThomas Zimmermann 	struct drm_fb_helper *helper = info->par;
82da6eb399SThomas Zimmermann 	struct drm_framebuffer *fb = helper->fb;
83da6eb399SThomas Zimmermann 	struct drm_gem_object *bo = drm_gem_fb_get_obj(fb, 0);
84da6eb399SThomas Zimmermann 
85da6eb399SThomas Zimmermann 	return drm_gem_mmap_obj(bo, omap_gem_mmap_size(bo), vma);
86da6eb399SThomas Zimmermann }
87da6eb399SThomas Zimmermann 
omap_fbdev_fb_destroy(struct fb_info * info)889e69bcd8SThomas Zimmermann static void omap_fbdev_fb_destroy(struct fb_info *info)
899e69bcd8SThomas Zimmermann {
909e69bcd8SThomas Zimmermann 	struct drm_fb_helper *helper = info->par;
919e69bcd8SThomas Zimmermann 	struct drm_framebuffer *fb = helper->fb;
929e69bcd8SThomas Zimmermann 	struct drm_gem_object *bo = drm_gem_fb_get_obj(fb, 0);
939e69bcd8SThomas Zimmermann 	struct omap_fbdev *fbdev = to_omap_fbdev(helper);
949e69bcd8SThomas Zimmermann 
959e69bcd8SThomas Zimmermann 	DBG();
969e69bcd8SThomas Zimmermann 
979e69bcd8SThomas Zimmermann 	drm_fb_helper_fini(helper);
989e69bcd8SThomas Zimmermann 
999e69bcd8SThomas Zimmermann 	omap_gem_unpin(bo);
1009e69bcd8SThomas Zimmermann 	drm_framebuffer_remove(fb);
1019e69bcd8SThomas Zimmermann 
1029e69bcd8SThomas Zimmermann 	drm_client_release(&helper->client);
1039e69bcd8SThomas Zimmermann 	drm_fb_helper_unprepare(helper);
1049e69bcd8SThomas Zimmermann 	kfree(fbdev);
1059e69bcd8SThomas Zimmermann }
1069e69bcd8SThomas Zimmermann 
107b6ff753aSJani Nikula static const struct fb_ops omap_fb_ops = {
1088bb0daffSRob Clark 	.owner = THIS_MODULE,
109*b21f187fSThomas Zimmermann 	__FB_DEFAULT_DMAMEM_OPS_RDWR,
110f9b34a0fSTomi Valkeinen 	.fb_check_var	= drm_fb_helper_check_var,
111f9b34a0fSTomi Valkeinen 	.fb_set_par	= drm_fb_helper_set_par,
112f9b34a0fSTomi Valkeinen 	.fb_setcmap	= drm_fb_helper_setcmap,
113f9b34a0fSTomi Valkeinen 	.fb_blank	= drm_fb_helper_blank,
114f9b34a0fSTomi Valkeinen 	.fb_pan_display = omap_fbdev_pan_display,
115*b21f187fSThomas Zimmermann 	__FB_DEFAULT_DMAMEM_OPS_DRAW,
116f9b34a0fSTomi Valkeinen 	.fb_ioctl	= drm_fb_helper_ioctl,
117da6eb399SThomas Zimmermann 	.fb_mmap	= omap_fbdev_fb_mmap,
1189e69bcd8SThomas Zimmermann 	.fb_destroy	= omap_fbdev_fb_destroy,
1198bb0daffSRob Clark };
1208bb0daffSRob Clark 
omap_fbdev_create(struct drm_fb_helper * helper,struct drm_fb_helper_surface_size * sizes)1218bb0daffSRob Clark static int omap_fbdev_create(struct drm_fb_helper *helper,
1228bb0daffSRob Clark 		struct drm_fb_helper_surface_size *sizes)
1238bb0daffSRob Clark {
1248bb0daffSRob Clark 	struct omap_fbdev *fbdev = to_omap_fbdev(helper);
1258bb0daffSRob Clark 	struct drm_device *dev = helper->dev;
1268bb0daffSRob Clark 	struct omap_drm_private *priv = dev->dev_private;
1278bb0daffSRob Clark 	struct drm_framebuffer *fb = NULL;
1288bb0daffSRob Clark 	union omap_gem_size gsize;
1298bb0daffSRob Clark 	struct fb_info *fbi = NULL;
1308bb0daffSRob Clark 	struct drm_mode_fb_cmd2 mode_cmd = {0};
131194c9e20SThomas Zimmermann 	struct drm_gem_object *bo;
13216869083SLaurent Pinchart 	dma_addr_t dma_addr;
1338bb0daffSRob Clark 	int ret;
1348bb0daffSRob Clark 
1358bb0daffSRob Clark 	sizes->surface_bpp = 32;
1361d977b06STomi Valkeinen 	sizes->surface_depth = 24;
1378bb0daffSRob Clark 
1388bb0daffSRob Clark 	DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width,
1398bb0daffSRob Clark 			sizes->surface_height, sizes->surface_bpp,
1408bb0daffSRob Clark 			sizes->fb_width, sizes->fb_height);
1418bb0daffSRob Clark 
1428bb0daffSRob Clark 	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
1438bb0daffSRob Clark 			sizes->surface_depth);
1448bb0daffSRob Clark 
1458bb0daffSRob Clark 	mode_cmd.width = sizes->surface_width;
1468bb0daffSRob Clark 	mode_cmd.height = sizes->surface_height;
1478bb0daffSRob Clark 
148ce481edaSTomi Valkeinen 	mode_cmd.pitches[0] =
149ce481edaSTomi Valkeinen 			DIV_ROUND_UP(mode_cmd.width * sizes->surface_bpp, 8);
1508bb0daffSRob Clark 
1518bb0daffSRob Clark 	fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled;
1528bb0daffSRob Clark 	if (fbdev->ywrap_enabled) {
1538bb0daffSRob Clark 		/* need to align pitch to page size if using DMM scrolling */
154743c1671SFabian Frederick 		mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]);
1558bb0daffSRob Clark 	}
1568bb0daffSRob Clark 
1578bb0daffSRob Clark 	/* allocate backing bo */
1588bb0daffSRob Clark 	gsize = (union omap_gem_size){
1598bb0daffSRob Clark 		.bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height),
1608bb0daffSRob Clark 	};
1618bb0daffSRob Clark 	DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index);
162194c9e20SThomas Zimmermann 	bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC);
163194c9e20SThomas Zimmermann 	if (!bo) {
1648bb0daffSRob Clark 		dev_err(dev->dev, "failed to allocate buffer object\n");
1658bb0daffSRob Clark 		ret = -ENOMEM;
1668bb0daffSRob Clark 		goto fail;
1678bb0daffSRob Clark 	}
1688bb0daffSRob Clark 
169194c9e20SThomas Zimmermann 	fb = omap_framebuffer_init(dev, &mode_cmd, &bo);
1708bb0daffSRob Clark 	if (IS_ERR(fb)) {
1718bb0daffSRob Clark 		dev_err(dev->dev, "failed to allocate fb\n");
1728bb0daffSRob Clark 		/* note: if fb creation failed, we can't rely on fb destroy
1738bb0daffSRob Clark 		 * to unref the bo:
1748bb0daffSRob Clark 		 */
175194c9e20SThomas Zimmermann 		drm_gem_object_put(bo);
1768bb0daffSRob Clark 		ret = PTR_ERR(fb);
1778bb0daffSRob Clark 		goto fail;
1788bb0daffSRob Clark 	}
1798bb0daffSRob Clark 
1808bb0daffSRob Clark 	/* note: this keeps the bo pinned.. which is perhaps not ideal,
1818bb0daffSRob Clark 	 * but is needed as long as we use fb_mmap() to mmap to userspace
1828bb0daffSRob Clark 	 * (since this happens using fix.smem_start).  Possibly we could
1838bb0daffSRob Clark 	 * implement our own mmap using GEM mmap support to avoid this
1848bb0daffSRob Clark 	 * (non-tiled buffer doesn't need to be pinned for fbcon to write
1858bb0daffSRob Clark 	 * to it).  Then we just need to be sure that we are able to re-
1868bb0daffSRob Clark 	 * pin it in case of an opps.
1878bb0daffSRob Clark 	 */
188194c9e20SThomas Zimmermann 	ret = omap_gem_pin(bo, &dma_addr);
1898bb0daffSRob Clark 	if (ret) {
190bc20c85cSLaurent Pinchart 		dev_err(dev->dev, "could not pin framebuffer\n");
1918bb0daffSRob Clark 		ret = -ENOMEM;
1928bb0daffSRob Clark 		goto fail;
1938bb0daffSRob Clark 	}
1948bb0daffSRob Clark 
1957fd50bc3SThomas Zimmermann 	fbi = drm_fb_helper_alloc_info(helper);
196231e6fafSArchit Taneja 	if (IS_ERR(fbi)) {
1978bb0daffSRob Clark 		dev_err(dev->dev, "failed to allocate fb info\n");
198231e6fafSArchit Taneja 		ret = PTR_ERR(fbi);
1993cbd0c58SLaurent Pinchart 		goto fail;
2008bb0daffSRob Clark 	}
2018bb0daffSRob Clark 
2028bb0daffSRob Clark 	DBG("fbi=%p, dev=%p", fbi, dev);
2038bb0daffSRob Clark 
2048bb0daffSRob Clark 	helper->fb = fb;
2058bb0daffSRob Clark 
2068bb0daffSRob Clark 	fbi->fbops = &omap_fb_ops;
2078bb0daffSRob Clark 
208e8f9ad5aSDaniel Vetter 	drm_fb_helper_fill_info(fbi, helper, sizes);
2098bb0daffSRob Clark 
210f98eb6c0SThomas Zimmermann 	fbi->flags |= FBINFO_VIRTFB;
211194c9e20SThomas Zimmermann 	fbi->screen_buffer = omap_gem_vaddr(bo);
212194c9e20SThomas Zimmermann 	fbi->screen_size = bo->size;
21316869083SLaurent Pinchart 	fbi->fix.smem_start = dma_addr;
214194c9e20SThomas Zimmermann 	fbi->fix.smem_len = bo->size;
2158bb0daffSRob Clark 
2168bb0daffSRob Clark 	/* if we have DMM, then we can use it for scrolling by just
2178bb0daffSRob Clark 	 * shuffling pages around in DMM rather than doing sw blit.
2188bb0daffSRob Clark 	 */
2198bb0daffSRob Clark 	if (fbdev->ywrap_enabled) {
2208bb0daffSRob Clark 		DRM_INFO("Enabling DMM ywrap scrolling\n");
2218bb0daffSRob Clark 		fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST;
2228bb0daffSRob Clark 		fbi->fix.ywrapstep = 1;
2238bb0daffSRob Clark 	}
2248bb0daffSRob Clark 
2258bb0daffSRob Clark 
2268bb0daffSRob Clark 	DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres);
22738129bc9SThomas Zimmermann 	DBG("allocated %dx%d fb", fb->width, fb->height);
2288bb0daffSRob Clark 
2298bb0daffSRob Clark 	return 0;
2308bb0daffSRob Clark 
2318bb0daffSRob Clark fail:
2328bb0daffSRob Clark 
2338bb0daffSRob Clark 	if (ret) {
234b77bc10bSDaniel Vetter 		if (fb)
2358bb0daffSRob Clark 			drm_framebuffer_remove(fb);
2368bb0daffSRob Clark 	}
2378bb0daffSRob Clark 
2388bb0daffSRob Clark 	return ret;
2398bb0daffSRob Clark }
2408bb0daffSRob Clark 
2413a493879SThierry Reding static const struct drm_fb_helper_funcs omap_fb_helper_funcs = {
2428bb0daffSRob Clark 	.fb_probe = omap_fbdev_create,
2438bb0daffSRob Clark };
2448bb0daffSRob Clark 
get_fb(struct fb_info * fbi)2458bb0daffSRob Clark static struct drm_fb_helper *get_fb(struct fb_info *fbi)
2468bb0daffSRob Clark {
2478bb0daffSRob Clark 	if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) {
2488bb0daffSRob Clark 		/* these are not the fb's you're looking for */
2498bb0daffSRob Clark 		return NULL;
2508bb0daffSRob Clark 	}
2518bb0daffSRob Clark 	return fbi->par;
2528bb0daffSRob Clark }
2538bb0daffSRob Clark 
2549e69bcd8SThomas Zimmermann /*
2559e69bcd8SThomas Zimmermann  * struct drm_client
2569e69bcd8SThomas Zimmermann  */
2578bb0daffSRob Clark 
omap_fbdev_client_unregister(struct drm_client_dev * client)2589e69bcd8SThomas Zimmermann static void omap_fbdev_client_unregister(struct drm_client_dev *client)
2599e69bcd8SThomas Zimmermann {
2609e69bcd8SThomas Zimmermann 	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
2619e69bcd8SThomas Zimmermann 
2629e69bcd8SThomas Zimmermann 	if (fb_helper->info) {
2639e69bcd8SThomas Zimmermann 		drm_fb_helper_unregister_info(fb_helper);
2649e69bcd8SThomas Zimmermann 	} else {
2659e69bcd8SThomas Zimmermann 		drm_client_release(&fb_helper->client);
2669e69bcd8SThomas Zimmermann 		drm_fb_helper_unprepare(fb_helper);
2679e69bcd8SThomas Zimmermann 		kfree(fb_helper);
2689e69bcd8SThomas Zimmermann 	}
2699e69bcd8SThomas Zimmermann }
2709e69bcd8SThomas Zimmermann 
omap_fbdev_client_restore(struct drm_client_dev * client)2719e69bcd8SThomas Zimmermann static int omap_fbdev_client_restore(struct drm_client_dev *client)
2729e69bcd8SThomas Zimmermann {
2739e69bcd8SThomas Zimmermann 	drm_fb_helper_lastclose(client->dev);
2749e69bcd8SThomas Zimmermann 
2759e69bcd8SThomas Zimmermann 	return 0;
2769e69bcd8SThomas Zimmermann }
2779e69bcd8SThomas Zimmermann 
omap_fbdev_client_hotplug(struct drm_client_dev * client)2789e69bcd8SThomas Zimmermann static int omap_fbdev_client_hotplug(struct drm_client_dev *client)
2799e69bcd8SThomas Zimmermann {
2809e69bcd8SThomas Zimmermann 	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
2819e69bcd8SThomas Zimmermann 	struct drm_device *dev = client->dev;
2829e69bcd8SThomas Zimmermann 	int ret;
2839e69bcd8SThomas Zimmermann 
2849e69bcd8SThomas Zimmermann 	if (dev->fb_helper)
2859e69bcd8SThomas Zimmermann 		return drm_fb_helper_hotplug_event(dev->fb_helper);
2869e69bcd8SThomas Zimmermann 
2879e69bcd8SThomas Zimmermann 	ret = drm_fb_helper_init(dev, fb_helper);
2889e69bcd8SThomas Zimmermann 	if (ret)
2899e69bcd8SThomas Zimmermann 		goto err_drm_err;
2909e69bcd8SThomas Zimmermann 
2919e69bcd8SThomas Zimmermann 	ret = drm_fb_helper_initial_config(fb_helper);
2929e69bcd8SThomas Zimmermann 	if (ret)
2939e69bcd8SThomas Zimmermann 		goto err_drm_fb_helper_fini;
2949e69bcd8SThomas Zimmermann 
2959e69bcd8SThomas Zimmermann 	return 0;
2969e69bcd8SThomas Zimmermann 
2979e69bcd8SThomas Zimmermann err_drm_fb_helper_fini:
2989e69bcd8SThomas Zimmermann 	drm_fb_helper_fini(fb_helper);
2999e69bcd8SThomas Zimmermann err_drm_err:
3009e69bcd8SThomas Zimmermann 	drm_err(dev, "Failed to setup fbdev emulation (ret=%d)\n", ret);
3019e69bcd8SThomas Zimmermann 	return ret;
3029e69bcd8SThomas Zimmermann }
3039e69bcd8SThomas Zimmermann 
3049e69bcd8SThomas Zimmermann static const struct drm_client_funcs omap_fbdev_client_funcs = {
3059e69bcd8SThomas Zimmermann 	.owner		= THIS_MODULE,
3069e69bcd8SThomas Zimmermann 	.unregister	= omap_fbdev_client_unregister,
3079e69bcd8SThomas Zimmermann 	.restore	= omap_fbdev_client_restore,
3089e69bcd8SThomas Zimmermann 	.hotplug	= omap_fbdev_client_hotplug,
3099e69bcd8SThomas Zimmermann };
3109e69bcd8SThomas Zimmermann 
omap_fbdev_setup(struct drm_device * dev)3119e69bcd8SThomas Zimmermann void omap_fbdev_setup(struct drm_device *dev)
3129e69bcd8SThomas Zimmermann {
3139e69bcd8SThomas Zimmermann 	struct omap_fbdev *fbdev;
3149e69bcd8SThomas Zimmermann 	struct drm_fb_helper *helper;
3159e69bcd8SThomas Zimmermann 	int ret;
3169e69bcd8SThomas Zimmermann 
3179e69bcd8SThomas Zimmermann 	drm_WARN(dev, !dev->registered, "Device has not been registered.\n");
3189e69bcd8SThomas Zimmermann 	drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n");
319da777721SPeter Ujfalusi 
3208bb0daffSRob Clark 	fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
321fffddfd6SLinus Torvalds 	if (!fbdev)
322c176060aSNathan Chancellor 		return;
3238bb0daffSRob Clark 	helper = &fbdev->base;
3248bb0daffSRob Clark 
3256c80a93bSThomas Zimmermann 	drm_fb_helper_prepare(dev, helper, 32, &omap_fb_helper_funcs);
3268bb0daffSRob Clark 
3279e69bcd8SThomas Zimmermann 	ret = drm_client_init(dev, &helper->client, "fbdev", &omap_fbdev_client_funcs);
328efd1f06bSTomi Valkeinen 	if (ret)
3299e69bcd8SThomas Zimmermann 		goto err_drm_client_init;
3308bb0daffSRob Clark 
3319e69bcd8SThomas Zimmermann 	INIT_WORK(&fbdev->work, pan_worker);
3329e69bcd8SThomas Zimmermann 
3339e69bcd8SThomas Zimmermann 	drm_client_register(&helper->client);
3348bb0daffSRob Clark 
335efd1f06bSTomi Valkeinen 	return;
3368bb0daffSRob Clark 
3379e69bcd8SThomas Zimmermann err_drm_client_init:
3383fb1f62fSThomas Zimmermann 	drm_fb_helper_unprepare(helper);
3398bb0daffSRob Clark 	kfree(fbdev);
3408bb0daffSRob Clark }
341