Create a simple fbdev device during SimpleDRM setup so legacy user-space and fbcon can use it. Original work by David Herrmann. Cc: dh.herrmann@xxxxxxxxx Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- Changes from version 3: - Remove #ifdef CONFIG_DRM_FBDEV_EMULATION - Use drm_fb_helper_set_suspend_lock() - Don't access the native framebuffer directly, but do blitting here as well. - Use the drm_fb_helper_sys_*() functions instead of the cfb versions. - Remove FBINFO_CAN_FORCE_OUTPUT flag which doesn't work now. - Pass struct drm_fb_helper around instead of struct sdrm_fbdev. Changes from version 2: - Switch to using drm_fb_helper in preparation for future panic handling which needs an enabled pipeline. Changes from version 1: No changes Changes from previous version: - Remove the DRM_SIMPLEDRM_FBDEV kconfig option and use DRM_FBDEV_EMULATION - Suspend fbcon/fbdev when the pipeline is enabled, resume in lastclose - Add FBINFO_CAN_FORCE_OUTPUT flag so we get oops'es on the console drivers/gpu/drm/simpledrm/Kconfig | 3 + drivers/gpu/drm/simpledrm/Makefile | 2 +- drivers/gpu/drm/simpledrm/simpledrm.h | 5 + drivers/gpu/drm/simpledrm/simpledrm_drv.c | 4 + drivers/gpu/drm/simpledrm/simpledrm_fbdev.c | 201 ++++++++++++++++++++++++++++ drivers/gpu/drm/simpledrm/simpledrm_kms.c | 14 ++ 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig index f45b25d..3257590 100644 --- a/drivers/gpu/drm/simpledrm/Kconfig +++ b/drivers/gpu/drm/simpledrm/Kconfig @@ -13,6 +13,9 @@ config DRM_SIMPLEDRM SimpleDRM supports "simple-framebuffer" DeviceTree objects and compatible platform framebuffers. + If fbdev support is enabled, this driver will also provide an fbdev + compatibility layer that supports fbcon, mmap is not supported. + If unsure, say Y. To compile this driver as a module, choose M here: the diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile index f6a62dc..5474f7f 100644 --- a/drivers/gpu/drm/simpledrm/Makefile +++ b/drivers/gpu/drm/simpledrm/Makefile @@ -1,4 +1,4 @@ simpledrm-y := simpledrm_drv.o simpledrm_kms.o simpledrm_gem.o \ - simpledrm_damage.o + simpledrm_damage.o simpledrm_fbdev.o obj-$(CONFIG_DRM_SIMPLEDRM) := simpledrm.o diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h index 0739581..d4eb52c 100644 --- a/drivers/gpu/drm/simpledrm/simpledrm.h +++ b/drivers/gpu/drm/simpledrm/simpledrm.h @@ -16,6 +16,7 @@ #include <drm/drm_simple_kms_helper.h> struct simplefb_format; +struct drm_fb_helper; struct regulator; struct clk; @@ -23,6 +24,7 @@ struct sdrm_device { struct drm_device *ddev; struct drm_simple_display_pipe pipe; struct drm_connector conn; + struct drm_fb_helper *fb_helper; /* framebuffer information */ const struct simplefb_format *fb_sformat; @@ -42,6 +44,7 @@ struct sdrm_device { struct regulator **regulators; }; +void sdrm_lastclose(struct drm_device *ddev); int sdrm_drm_modeset_init(struct sdrm_device *sdrm); int sdrm_dirty(struct drm_framebuffer *fb, @@ -82,5 +85,7 @@ struct sdrm_framebuffer { int sdrm_fb_init(struct drm_device *ddev, struct sdrm_framebuffer *fb, const struct drm_mode_fb_cmd2 *mode_cmd, struct sdrm_gem_object *obj); +void sdrm_fbdev_init(struct sdrm_device *sdrm); +void sdrm_fbdev_cleanup(struct sdrm_device *sdrm); #endif /* SDRM_DRV_H */ diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c index 17c1b55..fe752c6 100644 --- a/drivers/gpu/drm/simpledrm/simpledrm_drv.c +++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c @@ -47,6 +47,7 @@ static const struct vm_operations_struct sdrm_gem_vm_ops = { static struct drm_driver sdrm_drm_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, .fops = &sdrm_drm_fops, + .lastclose = sdrm_lastclose, .gem_free_object = sdrm_gem_free_object, .gem_vm_ops = &sdrm_gem_vm_ops, @@ -451,6 +452,8 @@ static int sdrm_simplefb_probe(struct platform_device *pdev) if (ret) goto err_regulators; + sdrm_fbdev_init(sdrm); + DRM_INFO("Initialized %s on minor %d\n", ddev->driver->name, ddev->primary->index); @@ -476,6 +479,7 @@ static int sdrm_simplefb_remove(struct platform_device *pdev) struct drm_device *ddev = platform_get_drvdata(pdev); struct sdrm_device *sdrm = ddev->dev_private; + sdrm_fbdev_cleanup(sdrm); drm_dev_unregister(ddev); drm_mode_config_cleanup(ddev); diff --git a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c new file mode 100644 index 0000000..c6596ad --- /dev/null +++ b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c @@ -0,0 +1,201 @@ +/* + * SimpleDRM firmware framebuffer driver + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx> + * + * 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. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <linux/fb.h> +#include <linux/platform_device.h> + +#include "simpledrm.h" + +struct sdrm_fbdev { + struct drm_fb_helper fb_helper; + struct sdrm_framebuffer fb; +}; + +static inline struct sdrm_fbdev *to_sdrm_fbdev(struct drm_fb_helper *helper) +{ + return container_of(helper, struct sdrm_fbdev, fb_helper); +} + +/* + * simpledrm uses the same gem code as udl and work on that driver has shown + * that it doens't work well with fb_deferred_io. So mmap is not supported. + * + * This is documented in commit 677d23b70bf9: + * + * drm/udl: disable fb_defio by default + * There seems to be a bad interaction between gem/shmem and defio on top, + * I get list corruption on the page lru in the shmem code. + * + * Turn it off for now until we get some more digging done. + * + */ +static int sdrm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + return -ENODEV; +} + +static struct fb_ops sdrm_fbdev_ops = { + .owner = THIS_MODULE, + .fb_fillrect = drm_fb_helper_sys_fillrect, + .fb_copyarea = drm_fb_helper_sys_copyarea, + .fb_imageblit = drm_fb_helper_sys_imageblit, + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_setcmap = drm_fb_helper_setcmap, + .fb_mmap = sdrm_fb_mmap, +}; + +static int sdrm_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct sdrm_fbdev *fbdev = to_sdrm_fbdev(helper); + struct drm_device *ddev = helper->dev; + struct sdrm_device *sdrm = ddev->dev_private; + struct drm_mode_fb_cmd2 mode_cmd = { + .width = sdrm->fb_width, + .height = sdrm->fb_height, + .pitches[0] = sdrm->fb_stride, + .pixel_format = sdrm->fb_format, + }; + struct sdrm_gem_object *obj; + struct drm_framebuffer *fb; + struct fb_info *fbi; + size_t size; + int ret; + + size = PAGE_ALIGN(sdrm->fb_size); + obj = sdrm_gem_alloc_object(ddev, size); + if (!obj) + return -ENOMEM; + + ret = sdrm_gem_vmap(obj); + if (ret) { + DRM_ERROR("failed to vmap fb\n"); + goto err_gem_free; + } + + fbi = drm_fb_helper_alloc_fbi(helper); + if (IS_ERR(fbi)) { + ret = PTR_ERR(fbi); + goto err_gem_free; + } + + ret = sdrm_fb_init(ddev, &fbdev->fb, &mode_cmd, obj); + if (ret) { + dev_err(ddev->dev, "Failed to init framebuffer: %d\n", ret); + goto err_fbi_release; + } + + fb = &fbdev->fb.base; + helper->fb = fb; + fbi->par = helper; + + fbi->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE; + fbi->fbops = &sdrm_fbdev_ops; + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height); + + strncpy(fbi->fix.id, "simpledrmfb", 15); + fbi->screen_base = obj->vmapping; + fbi->fix.smem_len = sdrm->fb_size; + + return 0; + +err_fbi_release: + drm_fb_helper_release_fbi(helper); + +err_gem_free: + drm_gem_object_unreference_unlocked(&obj->base); + + return ret; +} + +static const struct drm_fb_helper_funcs sdrm_fb_helper_funcs = { + .fb_probe = sdrm_fbdev_create, +}; + +void sdrm_fbdev_init(struct sdrm_device *sdrm) +{ + struct drm_device *ddev = sdrm->ddev; + struct drm_fb_helper *fb_helper; + struct sdrm_fbdev *fbdev; + int ret; + + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) { + dev_err(ddev->dev, "Failed to allocate drm fbdev.\n"); + return; + } + + fb_helper = &fbdev->fb_helper; + + drm_fb_helper_prepare(ddev, fb_helper, &sdrm_fb_helper_funcs); + + ret = drm_fb_helper_init(ddev, fb_helper, 1, 1); + if (ret < 0) { + dev_err(ddev->dev, "Failed to initialize drm fb helper.\n"); + goto err_free; + } + + ret = drm_fb_helper_single_add_all_connectors(fb_helper); + if (ret < 0) { + dev_err(ddev->dev, "Failed to add connectors.\n"); + goto err_drm_fb_helper_fini; + } + + ret = drm_fb_helper_initial_config(fb_helper, + ddev->mode_config.preferred_depth); + if (ret < 0) { + dev_err(ddev->dev, "Failed to set initial hw configuration.\n"); + goto err_drm_fb_helper_fini; + } + + if (!fb_helper->fbdev) { + /* fbdev emulation is disabled */ + kfree(fbdev); + return; + } + + sdrm->fb_helper = fb_helper; + + return; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(fb_helper); +err_free: + kfree(fbdev); +} + +void sdrm_fbdev_cleanup(struct sdrm_device *sdrm) +{ + struct drm_fb_helper *fb_helper = sdrm->fb_helper; + struct sdrm_fbdev *fbdev; + + if (!fb_helper) + return; + + sdrm->fb_helper = NULL; + fbdev = to_sdrm_fbdev(fb_helper); + + drm_fb_helper_unregister_fbi(fb_helper); + cancel_work_sync(&fb_helper->dirty_work); + drm_fb_helper_release_fbi(fb_helper); + + drm_framebuffer_unregister_private(fb_helper->fb); + drm_framebuffer_cleanup(fb_helper->fb); + drm_gem_object_unreference_unlocked(&fbdev->fb.obj->base); + + drm_fb_helper_fini(fb_helper); + kfree(fbdev); +} diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c index e6dc3df..8b98a08 100644 --- a/drivers/gpu/drm/simpledrm/simpledrm_kms.c +++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c @@ -12,6 +12,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> #include <drm/drm_gem.h> #include <drm/drm_simple_kms_helper.h> #include <linux/slab.h> @@ -24,6 +25,16 @@ static const uint32_t sdrm_formats[] = { DRM_FORMAT_XRGB8888, }; +void sdrm_lastclose(struct drm_device *ddev) +{ + struct sdrm_device *sdrm = ddev->dev_private; + + if (sdrm->fb_helper) { + drm_fb_helper_restore_fbdev_mode_unlocked(sdrm->fb_helper); + drm_fb_helper_set_suspend_lock(sdrm->fb_helper, 0); + } +} + static int sdrm_conn_get_modes(struct drm_connector *conn) { struct sdrm_device *sdrm = conn->dev->dev_private; @@ -92,6 +103,9 @@ void sdrm_display_pipe_update(struct drm_simple_display_pipe *pipe, sdrm_crtc_send_vblank_event(&pipe->crtc); + if (sdrm->fb_helper && fb && fb != sdrm->fb_helper->fb) + drm_fb_helper_set_suspend_lock(sdrm->fb_helper, 1); + if (fb) { pipe->plane.fb = fb; sdrm_dirty_all_locked(sdrm); -- 2.8.2 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html