diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/fbdev.c')
| -rw-r--r-- | drivers/gpu/drm/tegra/fbdev.c | 241 | 
1 files changed, 241 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tegra/fbdev.c b/drivers/gpu/drm/tegra/fbdev.c new file mode 100644 index 000000000000..dca9eccae466 --- /dev/null +++ b/drivers/gpu/drm/tegra/fbdev.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012-2013 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved. + * + * Based on the KMS/FB DMA helpers + *   Copyright (C) 2012 Analog Devices Inc. + */ + +#include <linux/console.h> +#include <linux/vmalloc.h> + +#include <drm/drm_drv.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_modeset_helper.h> + +#include "drm.h" +#include "gem.h" + +static int tegra_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ +	struct drm_fb_helper *helper = info->par; +	struct tegra_bo *bo; +	int err; + +	bo = tegra_fb_get_plane(helper->fb, 0); + +	err = drm_gem_mmap_obj(&bo->gem, bo->gem.size, vma); +	if (err < 0) +		return err; + +	return __tegra_gem_mmap(&bo->gem, vma); +} + +static void tegra_fbdev_fb_destroy(struct fb_info *info) +{ +	struct drm_fb_helper *helper = info->par; +	struct drm_framebuffer *fb = helper->fb; +	struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); + +	drm_fb_helper_fini(helper); + +	/* Undo the special mapping we made in fbdev probe. */ +	if (bo->pages) { +		vunmap(bo->vaddr); +		bo->vaddr = NULL; +	} +	drm_framebuffer_remove(fb); + +	drm_client_release(&helper->client); +	drm_fb_helper_unprepare(helper); +	kfree(helper); +} + +static const struct fb_ops tegra_fb_ops = { +	.owner = THIS_MODULE, +	DRM_FB_HELPER_DEFAULT_OPS, +	.fb_read = drm_fb_helper_sys_read, +	.fb_write = drm_fb_helper_sys_write, +	.fb_fillrect = drm_fb_helper_sys_fillrect, +	.fb_copyarea = drm_fb_helper_sys_copyarea, +	.fb_imageblit = drm_fb_helper_sys_imageblit, +	.fb_mmap = tegra_fb_mmap, +	.fb_destroy = tegra_fbdev_fb_destroy, +}; + +static int tegra_fbdev_probe(struct drm_fb_helper *helper, +			     struct drm_fb_helper_surface_size *sizes) +{ +	struct tegra_drm *tegra = helper->dev->dev_private; +	struct drm_device *drm = helper->dev; +	struct drm_mode_fb_cmd2 cmd = { 0 }; +	unsigned int bytes_per_pixel; +	struct drm_framebuffer *fb; +	unsigned long offset; +	struct fb_info *info; +	struct tegra_bo *bo; +	size_t size; +	int err; + +	bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + +	cmd.width = sizes->surface_width; +	cmd.height = sizes->surface_height; +	cmd.pitches[0] = round_up(sizes->surface_width * bytes_per_pixel, +				  tegra->pitch_align); + +	cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, +						     sizes->surface_depth); + +	size = cmd.pitches[0] * cmd.height; + +	bo = tegra_bo_create(drm, size, 0); +	if (IS_ERR(bo)) +		return PTR_ERR(bo); + +	info = drm_fb_helper_alloc_info(helper); +	if (IS_ERR(info)) { +		dev_err(drm->dev, "failed to allocate framebuffer info\n"); +		drm_gem_object_put(&bo->gem); +		return PTR_ERR(info); +	} + +	fb = tegra_fb_alloc(drm, &cmd, &bo, 1); +	if (IS_ERR(fb)) { +		err = PTR_ERR(fb); +		dev_err(drm->dev, "failed to allocate DRM framebuffer: %d\n", +			err); +		drm_gem_object_put(&bo->gem); +		return PTR_ERR(fb); +	} + +	helper->fb = fb; +	helper->info = info; + +	info->fbops = &tegra_fb_ops; + +	drm_fb_helper_fill_info(info, helper, sizes); + +	offset = info->var.xoffset * bytes_per_pixel + +		 info->var.yoffset * fb->pitches[0]; + +	if (bo->pages) { +		bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP, +				 pgprot_writecombine(PAGE_KERNEL)); +		if (!bo->vaddr) { +			dev_err(drm->dev, "failed to vmap() framebuffer\n"); +			err = -ENOMEM; +			goto destroy; +		} +	} + +	info->screen_base = (void __iomem *)bo->vaddr + offset; +	info->screen_size = size; +	info->fix.smem_start = (unsigned long)(bo->iova + offset); +	info->fix.smem_len = size; + +	return 0; + +destroy: +	drm_framebuffer_remove(fb); +	return err; +} + +static const struct drm_fb_helper_funcs tegra_fb_helper_funcs = { +	.fb_probe = tegra_fbdev_probe, +}; + +/* + * struct drm_client + */ + +static void tegra_fbdev_client_unregister(struct drm_client_dev *client) +{ +	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); + +	if (fb_helper->info) { +		drm_fb_helper_unregister_info(fb_helper); +	} else { +		drm_client_release(&fb_helper->client); +		drm_fb_helper_unprepare(fb_helper); +		kfree(fb_helper); +	} +} + +static int tegra_fbdev_client_restore(struct drm_client_dev *client) +{ +	drm_fb_helper_lastclose(client->dev); + +	return 0; +} + +static int tegra_fbdev_client_hotplug(struct drm_client_dev *client) +{ +	struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); +	struct drm_device *dev = client->dev; +	int ret; + +	if (dev->fb_helper) +		return drm_fb_helper_hotplug_event(dev->fb_helper); + +	ret = drm_fb_helper_init(dev, fb_helper); +	if (ret) +		goto err_drm_err; + +	if (!drm_drv_uses_atomic_modeset(dev)) +		drm_helper_disable_unused_functions(dev); + +	ret = drm_fb_helper_initial_config(fb_helper); +	if (ret) +		goto err_drm_fb_helper_fini; + +	return 0; + +err_drm_fb_helper_fini: +	drm_fb_helper_fini(fb_helper); +err_drm_err: +	drm_err(dev, "Failed to setup fbdev emulation (ret=%d)\n", ret); +	return ret; +} + +static const struct drm_client_funcs tegra_fbdev_client_funcs = { +	.owner		= THIS_MODULE, +	.unregister	= tegra_fbdev_client_unregister, +	.restore	= tegra_fbdev_client_restore, +	.hotplug	= tegra_fbdev_client_hotplug, +}; + +void tegra_fbdev_setup(struct drm_device *dev) +{ +	struct drm_fb_helper *helper; +	int ret; + +	drm_WARN(dev, !dev->registered, "Device has not been registered.\n"); +	drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n"); + +	helper = kzalloc(sizeof(*helper), GFP_KERNEL); +	if (!helper) +		return; +	drm_fb_helper_prepare(dev, helper, 32, &tegra_fb_helper_funcs); + +	ret = drm_client_init(dev, &helper->client, "fbdev", &tegra_fbdev_client_funcs); +	if (ret) +		goto err_drm_client_init; + +	ret = tegra_fbdev_client_hotplug(&helper->client); +	if (ret) +		drm_dbg_kms(dev, "client hotplug ret=%d\n", ret); + +	drm_client_register(&helper->client); + +	return; + +err_drm_client_init: +	drm_fb_helper_unprepare(helper); +	kfree(helper); +}  |