linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/5] drm: add SimpleDRM driver
@ 2016-08-22 20:25 Noralf Trønnes
  2016-08-22 20:25 ` [PATCH v4 1/5] of: Add EXPORT_SYMBOL for of_chosen Noralf Trønnes
                   ` (4 more replies)
  0 siblings, 5 replies; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-22 20:25 UTC (permalink / raw)
  To: dri-devel
  Cc: robh, devicetree, dh.herrmann, linux-kernel, Noralf Trønnes

This patchset adds the simpledrm driver by David Herrmann based on a
patchset[1] from 2014. That patchset also included patches for kicking
out simpledrm by real drivers. I have stayed away from that since it
involves another subsystem and I would probably be unable to answer any
questions about the implementation.

Two main changes in this fourth version:

The gem code has been reworked to match that of the udl driver. This
has led me to drop PRIME support since it was not trivial to implement.

The fbdev emulation doesn't use the native framebuffer directly, but uses
the framebuffer flushing/blitting functionality. This means that fbdev mmap
doesn't work anymore as noted in an udl commit message
(see note in simpledrm_fbdev.c).

Also added a patch to export of_chosen so the driver can be built as a module
and added drm_fb_helper_set_suspend_lock() that takes the console lock.

I have tested simpledrm on a Raspberry Pi B+ with U-boot setting up the
framebuffer and producing this node (legacy, not under /chosen):

/ {
        framebuffer@1e887000 {
                compatible = "simple-framebuffer";
                reg = <0x1e887000 0x36c600>;
                format = "r5g6b5";
                width = <1824>;
                height = <984>;
                stride = <3648>;
                status = "okay";
        };

I have only tested with fbcon and modetest (XR24,RG16).


Noralf.


Changes from version 3:
of:
- Export of_chosen so simpledrm can be built as a module
drm_fb_helper:
- Add drm_fb_helper_set_suspend_lock()
simpledrm:
- Reworked gem code to match udl
- Dropped PRIME support
- Dropped dirty_info_property, it's gone
- Don't use drm_device.platformdev it's deprecated
- Remove struct sdrm_device #ifdef's
- Split out sdrm_fb_init() from sdrm_fb_create(), needed by fbdev code
- Simplify drm_clip validation by extending the check in
  sdrm_dirty() and drop the one in sdrm_blit()
- Removed sdrm_dirty_all_unlocked() which was unused.
fbdev:
- 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.
remove_conflicting_framebuffers:
- drm_device.platformdev is deprecated, use to_platform_device(ddev->dev).
- fb_helper might have been released in sdrm_fbdev_fb_destroy(),
  so open code drm_fb_helper_release_fbi()
- Strengthen the test in sdrm_fbdev_event_notify() that we're the one.

Changes from version 2:
- Remove superfluos module.h includes
- Move includes from header to source files
- Set plane.fb before flushing in pipe update, or else the previous one
  gets flushed
- Added check for vblank event in sdrm_display_pipe_update()
fbdev:
- Switch to using drm_fb_helper in preparation for future panic handling
  which needs an enabled pipeline.
- Don't forget to free fb_info when kicked out.

Changes from version 1:
- Move platform_set_drvdata() before drm_dev_register()
- Remove drm_legacy_mmap() call.
- Set mode_config.{min,max}_{width,height} to the actual dimensions
  of the native framebuffer
- Remove plane positioning since it won't work with the simple display pipe,
  meaning sdrm_display_pipe_check() isn't necessary either
- Support the additions to the Device Tree binding document, including
  clocks, regulators and having the node under /chosen
fbdev:
- Honour remove_conflicting_framebuffers()

Changes from previous version[2]:
- Remove FB_SIMPLE=n dependency to avoid kconfig recursive error
- Changed module name to match kconfig help text: sdrm -> simpledrm
- Use drm_simple_display_pipe
- Replace deprecated drm_platform_init()
- sdrm_dumb_create(): drm_gem_object_unreference() -> *_unlocked()
- sdrm_dumb_map_offset(): drm_gem_object_lookup() remove drm_device parameter
- sdrm_drm_mmap() changes:
  Remove struct_mutex locking
  Add drm_vma_offset_{lock,unlock}_lookup()
  drm_mmap() -> drm_legacy_mmap()
- dma_buf_begin_cpu_access() doesn't require start and length anymore
- Use drm_cvt_mode() instead of open coding a mode
- Fix format conversion. In the intermediate step, store the 8/6/5 bit color
  value in the upper part of the 16-bit color variable, not the lower.
- Support clips == NULL in sdrm_dirty()
- Set mode_config.preferred_depth
- Attach mode_config.dirty_info_property to connector
fbdev:
- 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

[1] https://lists.freedesktop.org/archives/dri-devel/2014-January/052584.html
[2] https://lists.freedesktop.org/archives/dri-devel/2014-January/052594.html


Noralf Trønnes (5):
  of: Add EXPORT_SYMBOL for of_chosen
  drm/fb-helper: Add drm_fb_helper_set_suspend_lock()
  drm: add SimpleDRM driver
  drm: simpledrm: add fbdev fallback support
  drm: simpledrm: honour remove_conflicting_framebuffers()

 drivers/gpu/drm/Kconfig                      |   2 +
 drivers/gpu/drm/Makefile                     |   1 +
 drivers/gpu/drm/drm_fb_helper.c              |  57 +++
 drivers/gpu/drm/simpledrm/Kconfig            |  27 ++
 drivers/gpu/drm/simpledrm/Makefile           |   4 +
 drivers/gpu/drm/simpledrm/simpledrm.h        |  93 +++++
 drivers/gpu/drm/simpledrm/simpledrm_damage.c | 235 ++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_drv.c    | 550 +++++++++++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c  | 259 +++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_gem.c    | 202 ++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_kms.c    | 248 ++++++++++++
 drivers/of/base.c                            |   1 +
 include/drm/drm_fb_helper.h                  |   9 +
 13 files changed, 1688 insertions(+)
 create mode 100644 drivers/gpu/drm/simpledrm/Kconfig
 create mode 100644 drivers/gpu/drm/simpledrm/Makefile
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm.h
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_damage.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_drv.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_gem.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_kms.c

--
2.8.2

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v4 1/5] of: Add EXPORT_SYMBOL for of_chosen
  2016-08-22 20:25 [PATCH v4 0/5] drm: add SimpleDRM driver Noralf Trønnes
@ 2016-08-22 20:25 ` Noralf Trønnes
  2016-08-22 20:25 ` [PATCH v4 2/5] drm/fb-helper: Add drm_fb_helper_set_suspend_lock() Noralf Trønnes
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-22 20:25 UTC (permalink / raw)
  To: dri-devel
  Cc: robh, devicetree, dh.herrmann, linux-kernel, Noralf Trønnes

Export of_chosen so drivers built as modules can get access to it.
The simpledrm driver will use this and is compatible with
simple-framebuffer which is a subnode of /chosen.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/of/base.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/of/base.c b/drivers/of/base.c
index 7792266..489be01 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -38,6 +38,7 @@ LIST_HEAD(aliases_lookup);
 struct device_node *of_root;
 EXPORT_SYMBOL(of_root);
 struct device_node *of_chosen;
+EXPORT_SYMBOL(of_chosen);
 struct device_node *of_aliases;
 struct device_node *of_stdout;
 static const char *of_stdout_options;
-- 
2.8.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v4 2/5] drm/fb-helper: Add drm_fb_helper_set_suspend_lock()
  2016-08-22 20:25 [PATCH v4 0/5] drm: add SimpleDRM driver Noralf Trønnes
  2016-08-22 20:25 ` [PATCH v4 1/5] of: Add EXPORT_SYMBOL for of_chosen Noralf Trønnes
@ 2016-08-22 20:25 ` Noralf Trønnes
  2016-08-23  6:07   ` Daniel Vetter
  2016-08-22 20:25 ` [PATCH v4 3/5] drm: add SimpleDRM driver Noralf Trønnes
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-22 20:25 UTC (permalink / raw)
  To: dri-devel
  Cc: robh, devicetree, dh.herrmann, linux-kernel, Noralf Trønnes

This adds a function that also takes the console lock before calling
fb_set_suspend() in contrast to drm_fb_helper_set_suspend() which is
a plain wrapper around fb_set_suspend().
Resume is run asynchronously using a worker if the console lock is
already taken. This is modelled after the i915 driver.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_fb_helper.c | 57 +++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_fb_helper.h     |  9 +++++++
 2 files changed, 66 insertions(+)

diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index ce54e98..897e6f4 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -29,6 +29,7 @@
  */
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#include <linux/console.h>
 #include <linux/kernel.h>
 #include <linux/sysrq.h>
 #include <linux/slab.h>
@@ -618,6 +619,16 @@ static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
 	kfree(helper->crtc_info);
 }
 
+static void drm_fb_helper_resume_worker(struct work_struct *work)
+{
+	struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper,
+						    resume_work);
+
+	console_lock();
+	fb_set_suspend(helper->fbdev, 0);
+	console_unlock();
+}
+
 static void drm_fb_helper_dirty_work(struct work_struct *work)
 {
 	struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper,
@@ -649,6 +660,7 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
 {
 	INIT_LIST_HEAD(&helper->kernel_fb_list);
 	spin_lock_init(&helper->dirty_lock);
+	INIT_WORK(&helper->resume_work, drm_fb_helper_resume_worker);
 	INIT_WORK(&helper->dirty_work, drm_fb_helper_dirty_work);
 	helper->dirty_clip.x1 = helper->dirty_clip.y1 = ~0;
 	helper->funcs = funcs;
@@ -1035,6 +1047,51 @@ void drm_fb_helper_set_suspend(struct drm_fb_helper *fb_helper, int state)
 }
 EXPORT_SYMBOL(drm_fb_helper_set_suspend);
 
+/**
+ * drm_fb_helper_set_suspend_lock - wrapper around fb_set_suspend that also
+ *                                  takes the console lock
+ * @fb_helper: driver-allocated fbdev helper
+ * @state: desired state, zero to resume, non-zero to suspend
+ *
+ * A wrapper around fb_set_suspend() that takes the console lock. If the lock
+ * isn't available on resume, a worker is tasked with waiting for the lock
+ * to become available. The console lock can be pretty contented on resume
+ * due to all the printk activity.
+ *
+ * This function can be called multiple times with the same state since
+ * &fb_info->state is checked to see if fbdev is running or not before locking.
+ *
+ * Use drm_fb_helper_set_suspend() if you need to take the lock yourself.
+ */
+void drm_fb_helper_set_suspend_lock(struct drm_fb_helper *fb_helper, int state)
+{
+	if (!fb_helper || !fb_helper->fbdev)
+		return;
+
+	/* make sure there's no pending/ongoing resume */
+	flush_work(&fb_helper->resume_work);
+
+	if (state) { /* suspend */
+		if (fb_helper->fbdev->state != FBINFO_STATE_RUNNING)
+			return;
+
+		console_lock();
+
+	} else { /* resume */
+		if (fb_helper->fbdev->state == FBINFO_STATE_RUNNING)
+			return;
+
+		if (!console_trylock()) {
+			schedule_work(&fb_helper->resume_work);
+			return;
+		}
+	}
+
+	fb_set_suspend(fb_helper->fbdev, state);
+	console_unlock();
+}
+EXPORT_SYMBOL(drm_fb_helper_set_suspend_lock);
+
 static int setcolreg(struct drm_crtc *crtc, u16 red, u16 green,
 		     u16 blue, u16 regno, struct fb_info *info)
 {
diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
index db8d478..db7e861 100644
--- a/include/drm/drm_fb_helper.h
+++ b/include/drm/drm_fb_helper.h
@@ -176,6 +176,7 @@ struct drm_fb_helper_connector {
  *              the screen buffer
  * @dirty_lock: spinlock protecting @dirty_clip
  * @dirty_work: worker used to flush the framebuffer
+ * @resume_work: worker used during resume if the console lock is already taken
  *
  * This is the main structure used by the fbdev helpers. Drivers supporting
  * fbdev emulation should embedded this into their overall driver structure.
@@ -196,6 +197,7 @@ struct drm_fb_helper {
 	struct drm_clip_rect dirty_clip;
 	spinlock_t dirty_lock;
 	struct work_struct dirty_work;
+	struct work_struct resume_work;
 
 	/**
 	 * @kernel_fb_list:
@@ -264,6 +266,8 @@ void drm_fb_helper_cfb_imageblit(struct fb_info *info,
 				 const struct fb_image *image);
 
 void drm_fb_helper_set_suspend(struct drm_fb_helper *fb_helper, int state);
+void drm_fb_helper_set_suspend_lock(struct drm_fb_helper *fb_helper,
+				    int state);
 
 int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info);
 
@@ -421,6 +425,11 @@ static inline void drm_fb_helper_set_suspend(struct drm_fb_helper *fb_helper,
 {
 }
 
+static inline void
+drm_fb_helper_set_suspend_lock(struct drm_fb_helper *fb_helper, int state)
+{
+}
+
 static inline int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
 {
 	return 0;
-- 
2.8.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-08-22 20:25 [PATCH v4 0/5] drm: add SimpleDRM driver Noralf Trønnes
  2016-08-22 20:25 ` [PATCH v4 1/5] of: Add EXPORT_SYMBOL for of_chosen Noralf Trønnes
  2016-08-22 20:25 ` [PATCH v4 2/5] drm/fb-helper: Add drm_fb_helper_set_suspend_lock() Noralf Trønnes
@ 2016-08-22 20:25 ` Noralf Trønnes
  2016-08-23  6:17   ` Daniel Vetter
                     ` (2 more replies)
  2016-08-22 20:25 ` [PATCH v4 4/5] drm: simpledrm: add fbdev fallback support Noralf Trønnes
  2016-08-22 20:25 ` [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers() Noralf Trønnes
  4 siblings, 3 replies; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-22 20:25 UTC (permalink / raw)
  To: dri-devel
  Cc: robh, devicetree, dh.herrmann, linux-kernel, Noralf Trønnes, libv

The SimpleDRM driver binds to simple-framebuffer devices and provides a
DRM/KMS API. It provides only a single CRTC+encoder+connector combination
plus one initial mode.

Userspace can create dumb-buffers which can be blit into the real
framebuffer similar to UDL. No access to the real framebuffer is allowed
(compared to earlier version of this driver) to avoid security issues.
Furthermore, this way we can support arbitrary modes as long as we have a
conversion-helper.

The driver was originally written by David Herrmann in 2014.
My main contribution is to make use of drm_simple_kms_helper and
rework the probe path to avoid use of the deprecated drm_platform_init()
and drm_driver.{load,unload}().
Additions have also been made for later changes to the Device Tree binding
document, like support for clocks, regulators and having the node under
/chosen. This code was lifted verbatim from simplefb.c.

Cc: dh.herrmann@gmail.com
Cc: libv@skynet.be
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---

Changes from version 3:
- Reworked gem code to match udl
- Dropped PRIME support
- Dropped dirty_info_property, it's gone
- Don't use drm_device.platformdev it's deprecated
- Remove struct sdrm_device #ifdef's
- Split out sdrm_fb_init() from sdrm_fb_create(), needed by fbdev code
- Simplify drm_clip validation by extending the check in
  sdrm_dirty() and drop the one in sdrm_blit()
- Removed sdrm_dirty_all_unlocked() which was unused.

Changes from version 2:
- Remove superfluos module.h includes
- Move includes from header to source files
- Set plane.fb before flushing in pipe update, or else the previous one
  gets flushed
- Added check for vblank event in sdrm_display_pipe_update()

Changes from version 1:
- Move platform_set_drvdata() before drm_dev_register()
- Remove drm_legacy_mmap() call.
- Set mode_config.{min,max}_{width,height} to the actual dimensions
  of the native framebuffer
- Remove plane positioning since it won't work with the simple display pipe,
  meaning sdrm_display_pipe_check() isn't necessary either
- Support the additions to the Device Tree binding document, including
  clocks, regulators and having the node under /chosen

Changes from previous version:
- Remove FB_SIMPLE=n dependency to avoid kconfig recursive error
- Changed module name to match kconfig help text: sdrm -> simpledrm
- Use drm_simple_display_pipe
- Replace deprecated drm_platform_init()
- sdrm_dumb_create(): drm_gem_object_unreference() -> *_unlocked()
- sdrm_dumb_map_offset(): drm_gem_object_lookup() remove drm_device parameter
- sdrm_drm_mmap() changes:
  Remove struct_mutex locking
  Add drm_vma_offset_{lock,unlock}_lookup()
  drm_mmap() -> drm_legacy_mmap()
- dma_buf_begin_cpu_access() doesn't require start and length anymore
- Use drm_cvt_mode() instead of open coding a mode
- Fix format conversion. In the intermediate step, store the 8/6/5 bit color
  value in the upper part of the 16-bit color variable, not the lower.
- Support clips == NULL in sdrm_dirty()
- Set mode_config.preferred_depth
- Attach mode_config.dirty_info_property to connector

 drivers/gpu/drm/Kconfig                      |   2 +
 drivers/gpu/drm/Makefile                     |   1 +
 drivers/gpu/drm/simpledrm/Kconfig            |  19 +
 drivers/gpu/drm/simpledrm/Makefile           |   4 +
 drivers/gpu/drm/simpledrm/simpledrm.h        |  86 +++++
 drivers/gpu/drm/simpledrm/simpledrm_damage.c | 235 ++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_drv.c    | 543 +++++++++++++++++++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_gem.c    | 202 ++++++++++
 drivers/gpu/drm/simpledrm/simpledrm_kms.c    | 234 ++++++++++++
 9 files changed, 1326 insertions(+)
 create mode 100644 drivers/gpu/drm/simpledrm/Kconfig
 create mode 100644 drivers/gpu/drm/simpledrm/Makefile
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm.h
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_damage.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_drv.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_gem.c
 create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_kms.c

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index fc35731..a54cc8d 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -290,3 +290,5 @@ source "drivers/gpu/drm/arc/Kconfig"
 source "drivers/gpu/drm/hisilicon/Kconfig"

 source "drivers/gpu/drm/mediatek/Kconfig"
+
+source "drivers/gpu/drm/simpledrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index e3dba6f..eba32ad 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -83,3 +83,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
 obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
+obj-$(CONFIG_DRM_SIMPLEDRM) += simpledrm/
diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
new file mode 100644
index 0000000..f45b25d
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Kconfig
@@ -0,0 +1,19 @@
+config DRM_SIMPLEDRM
+	tristate "Simple firmware framebuffer DRM driver"
+	depends on DRM
+	select DRM_KMS_HELPER
+	help
+	  SimpleDRM can run on all systems with pre-initialized graphics
+	  hardware. It uses a framebuffer that was initialized during
+	  firmware boot. No page-flipping, modesetting or other advanced
+	  features are available. However, other DRM drivers can be loaded
+	  later and take over from SimpleDRM if they provide real hardware
+	  support.
+
+	  SimpleDRM supports "simple-framebuffer" DeviceTree objects and
+	  compatible platform framebuffers.
+
+	  If unsure, say Y.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called simpledrm.
diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
new file mode 100644
index 0000000..f6a62dc
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/Makefile
@@ -0,0 +1,4 @@
+simpledrm-y :=	simpledrm_drv.o simpledrm_kms.o simpledrm_gem.o \
+		simpledrm_damage.o
+
+obj-$(CONFIG_DRM_SIMPLEDRM) := simpledrm.o
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
new file mode 100644
index 0000000..0739581
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -0,0 +1,86 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef SDRM_DRV_H
+#define SDRM_DRV_H
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+struct simplefb_format;
+struct regulator;
+struct clk;
+
+struct sdrm_device {
+	struct drm_device *ddev;
+	struct drm_simple_display_pipe pipe;
+	struct drm_connector conn;
+
+	/* framebuffer information */
+	const struct simplefb_format *fb_sformat;
+	u32 fb_format;
+	u32 fb_width;
+	u32 fb_height;
+	u32 fb_stride;
+	u32 fb_bpp;
+	unsigned long fb_base;
+	unsigned long fb_size;
+	void *fb_map;
+
+	unsigned int clk_count;
+	struct clk **clks;
+
+	u32 regulator_count;
+	struct regulator **regulators;
+};
+
+int sdrm_drm_modeset_init(struct sdrm_device *sdrm);
+
+int sdrm_dirty(struct drm_framebuffer *fb,
+	       struct drm_file *file,
+	       unsigned int flags, unsigned int color,
+	       struct drm_clip_rect *clips,
+	       unsigned int num_clips);
+int sdrm_dirty_all_locked(struct sdrm_device *sdrm);
+
+struct sdrm_gem_object {
+	struct drm_gem_object base;
+	struct page **pages;
+	void *vmapping;
+};
+
+#define to_sdrm_bo(x) container_of(x, struct sdrm_gem_object, base)
+
+int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma);
+int sdrm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
+int sdrm_gem_vmap(struct sdrm_gem_object *sobj);
+
+struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
+					      size_t size);
+void sdrm_gem_free_object(struct drm_gem_object *obj);
+
+int sdrm_dumb_create(struct drm_file *file_priv, struct drm_device *ddev,
+		     struct drm_mode_create_dumb *arg);
+int sdrm_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev,
+			 uint32_t handle, uint64_t *offset);
+
+struct sdrm_framebuffer {
+	struct drm_framebuffer base;
+	struct sdrm_gem_object *obj;
+};
+
+#define to_sdrm_fb(x) container_of(x, struct sdrm_framebuffer, base)
+
+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);
+
+#endif /* SDRM_DRV_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_damage.c b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
new file mode 100644
index 0000000..52c845f
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
@@ -0,0 +1,235 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * 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 <asm/unaligned.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <linux/dma-buf.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "simpledrm.h"
+
+static inline void sdrm_put(u8 *dst, u32 four_cc, u16 r, u16 g, u16 b)
+{
+	switch (four_cc) {
+	case DRM_FORMAT_RGB565:
+		r >>= 11;
+		g >>= 10;
+		b >>= 11;
+		put_unaligned((u16)((r << 11) | (g << 5) | b), (u16 *)dst);
+		break;
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+		r >>= 11;
+		g >>= 11;
+		b >>= 11;
+		put_unaligned((u16)((r << 10) | (g << 5) | b), (u16 *)dst);
+		break;
+	case DRM_FORMAT_RGB888:
+		r >>= 8;
+		g >>= 8;
+		b >>= 8;
+#ifdef __LITTLE_ENDIAN
+		dst[2] = r;
+		dst[1] = g;
+		dst[0] = b;
+#elif defined(__BIG_ENDIAN)
+		dst[0] = r;
+		dst[1] = g;
+		dst[2] = b;
+#endif
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		r >>= 8;
+		g >>= 8;
+		b >>= 8;
+		put_unaligned((u32)((r << 16) | (g << 8) | b), (u32 *)dst);
+		break;
+	case DRM_FORMAT_ABGR8888:
+		r >>= 8;
+		g >>= 8;
+		b >>= 8;
+		put_unaligned((u32)((b << 16) | (g << 8) | r), (u32 *)dst);
+		break;
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+		r >>= 4;
+		g >>= 4;
+		b >>= 4;
+		put_unaligned((u32)((r << 20) | (g << 10) | b), (u32 *)dst);
+		break;
+	}
+}
+
+static void sdrm_blit_from_xrgb8888(const u8 *src, u32 src_stride, u32 src_bpp,
+				    u8 *dst, u32 dst_stride, u32 dst_bpp,
+				    u32 dst_four_cc, u32 width, u32 height)
+{
+	u32 val, i;
+
+	while (height--) {
+		for (i = 0; i < width; ++i) {
+			val = get_unaligned((const u32 *)&src[i * src_bpp]);
+			sdrm_put(&dst[i * dst_bpp], dst_four_cc,
+				 (val & 0x00ff0000U) >> 8,
+				 (val & 0x0000ff00U),
+				 (val & 0x000000ffU) << 8);
+		}
+
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit_from_rgb565(const u8 *src, u32 src_stride, u32 src_bpp,
+				  u8 *dst, u32 dst_stride, u32 dst_bpp,
+				  u32 dst_four_cc, u32 width, u32 height)
+{
+	u32 val, i;
+
+	while (height--) {
+		for (i = 0; i < width; ++i) {
+			val = get_unaligned((const u16 *)&src[i * src_bpp]);
+			sdrm_put(&dst[i * dst_bpp], dst_four_cc,
+				 (val & 0xf800),
+				 (val & 0x07e0) << 5,
+				 (val & 0x001f) << 11);
+		}
+
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit_lines(const u8 *src, u32 src_stride,
+			    u8 *dst, u32 dst_stride,
+			    u32 bpp, u32 width, u32 height)
+{
+	u32 len;
+
+	len = width * bpp;
+
+	while (height--) {
+		memcpy(dst, src, len);
+		src += src_stride;
+		dst += dst_stride;
+	}
+}
+
+static void sdrm_blit(struct sdrm_framebuffer *sfb, u32 x, u32 y,
+		      u32 width, u32 height)
+{
+	struct drm_framebuffer *fb = &sfb->base;
+	struct drm_device *ddev = fb->dev;
+	struct sdrm_device *sdrm = ddev->dev_private;
+	u32 src_bpp, dst_bpp;
+	u8 *src, *dst;
+
+	/* already unmapped; ongoing handover? */
+	if (!sdrm->fb_map)
+		return;
+
+	/* get buffer offsets */
+	src = sfb->obj->vmapping;
+	dst = sdrm->fb_map;
+
+	/* bo is guaranteed to be big enough; size checks not needed */
+	src_bpp = (fb->bits_per_pixel + 7) / 8;
+	src += fb->offsets[0] + y * fb->pitches[0] + x * src_bpp;
+
+	dst_bpp = (sdrm->fb_bpp + 7) / 8;
+	dst += y * sdrm->fb_stride + x * dst_bpp;
+
+	/* if formats are identical, do a line-by-line copy.. */
+	if (fb->pixel_format == sdrm->fb_format) {
+		sdrm_blit_lines(src, fb->pitches[0],
+				dst, sdrm->fb_stride,
+				src_bpp, width, height);
+		return;
+	}
+
+	/* ..otherwise call slow blit-function */
+	switch (fb->pixel_format) {
+	case DRM_FORMAT_ARGB8888:
+		/* fallthrough */
+	case DRM_FORMAT_XRGB8888:
+		sdrm_blit_from_xrgb8888(src, fb->pitches[0], src_bpp,
+					dst, sdrm->fb_stride, dst_bpp,
+					sdrm->fb_format, width, height);
+		break;
+	case DRM_FORMAT_RGB565:
+		sdrm_blit_from_rgb565(src, fb->pitches[0], src_bpp,
+				      dst, sdrm->fb_stride, dst_bpp,
+				      sdrm->fb_format, width, height);
+		break;
+	}
+}
+
+int sdrm_dirty(struct drm_framebuffer *fb,
+	       struct drm_file *file,
+	       unsigned int flags, unsigned int color,
+	       struct drm_clip_rect *clips,
+	       unsigned int num_clips)
+{
+	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+	struct drm_device *ddev = fb->dev;
+	struct sdrm_device *sdrm = ddev->dev_private;
+	struct drm_clip_rect full_clip;
+	unsigned int i;
+	int r;
+
+	if (!clips || !num_clips) {
+		full_clip.x1 = 0;
+		full_clip.x2 = fb->width;
+		full_clip.y1 = 0;
+		full_clip.y2 = fb->height;
+		clips = &full_clip;
+		num_clips = 1;
+	}
+
+	drm_modeset_lock_all(ddev);
+
+	if (sdrm->pipe.plane.fb != fb) {
+		r = 0;
+		goto unlock;
+	}
+
+	for (i = 0; i < num_clips; i++) {
+		if (clips[i].x1 > clips[i].x2 || clips[i].x2 > fb->width ||
+		    clips[i].y1 > clips[i].y2 || clips[i].y2 > fb->height)
+			continue;
+
+		sdrm_blit(sfb, clips[i].x1, clips[i].y1,
+			  clips[i].x2 - clips[i].x1,
+			  clips[i].y2 - clips[i].y1);
+	}
+
+unlock:
+	drm_modeset_unlock_all(ddev);
+	return 0;
+}
+
+int sdrm_dirty_all_locked(struct sdrm_device *sdrm)
+{
+	struct drm_framebuffer *fb;
+	struct sdrm_framebuffer *sfb;
+
+	fb = sdrm->pipe.plane.fb;
+	if (!fb)
+		return 0;
+
+	sfb = to_sdrm_fb(fb);
+
+	sdrm_blit(sfb, 0, 0, fb->width, fb->height);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
new file mode 100644
index 0000000..17c1b55
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -0,0 +1,543 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * 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 <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+
+#include "simpledrm.h"
+
+static const struct file_operations sdrm_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = drm_open,
+	.mmap = sdrm_drm_mmap,
+	.poll = drm_poll,
+	.read = drm_read,
+	.unlocked_ioctl = drm_ioctl,
+	.release = drm_release,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = drm_compat_ioctl,
+#endif
+	.llseek = noop_llseek,
+};
+
+static const struct vm_operations_struct sdrm_gem_vm_ops = {
+	.fault = sdrm_gem_fault,
+	.open = drm_gem_vm_open,
+	.close = drm_gem_vm_close,
+};
+
+static struct drm_driver sdrm_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.fops = &sdrm_drm_fops,
+
+	.gem_free_object = sdrm_gem_free_object,
+	.gem_vm_ops = &sdrm_gem_vm_ops,
+
+	.dumb_create = sdrm_dumb_create,
+	.dumb_map_offset = sdrm_dumb_map_offset,
+	.dumb_destroy = drm_gem_dumb_destroy,
+
+	.name = "simpledrm",
+	.desc = "Simple firmware framebuffer DRM driver",
+	.date = "20130601",
+	.major = 0,
+	.minor = 0,
+	.patchlevel = 1,
+};
+
+#if defined CONFIG_OF && defined CONFIG_COMMON_CLK
+/*
+ * Clock handling code.
+ *
+ * Here we handle the clocks property of our "simple-framebuffer" dt node.
+ * This is necessary so that we can make sure that any clocks needed by
+ * the display engine that the bootloader set up for us (and for which it
+ * provided a simplefb dt node), stay up, for the life of the simplefb
+ * driver.
+ *
+ * When the driver unloads, we cleanly disable, and then release the clocks.
+ *
+ * We only complain about errors here, no action is taken as the most likely
+ * error can only happen due to a mismatch between the bootloader which set
+ * up simplefb, and the clock definitions in the device tree. Chances are
+ * that there are no adverse effects, and if there are, a clean teardown of
+ * the fb probe will not help us much either. So just complain and carry on,
+ * and hope that the user actually gets a working fb at the end of things.
+ */
+static int sdrm_clocks_init(struct sdrm_device *sdrm,
+			    struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct clk *clock;
+	int i, ret;
+
+	if (dev_get_platdata(&pdev->dev) || !np)
+		return 0;
+
+	sdrm->clk_count = of_clk_get_parent_count(np);
+	if (!sdrm->clk_count)
+		return 0;
+
+	sdrm->clks = kcalloc(sdrm->clk_count, sizeof(struct clk *), GFP_KERNEL);
+	if (!sdrm->clks)
+		return -ENOMEM;
+
+	for (i = 0; i < sdrm->clk_count; i++) {
+		clock = of_clk_get(np, i);
+		if (IS_ERR(clock)) {
+			if (PTR_ERR(clock) == -EPROBE_DEFER) {
+				while (--i >= 0) {
+					if (sdrm->clks[i])
+						clk_put(sdrm->clks[i]);
+				}
+				kfree(sdrm->clks);
+				return -EPROBE_DEFER;
+			}
+			dev_err(&pdev->dev, "%s: clock %d not found: %ld\n",
+				__func__, i, PTR_ERR(clock));
+			continue;
+		}
+		sdrm->clks[i] = clock;
+	}
+
+	for (i = 0; i < sdrm->clk_count; i++) {
+		if (sdrm->clks[i]) {
+			ret = clk_prepare_enable(sdrm->clks[i]);
+			if (ret) {
+				dev_err(&pdev->dev,
+					"%s: failed to enable clock %d: %d\n",
+					__func__, i, ret);
+				clk_put(sdrm->clks[i]);
+				sdrm->clks[i] = NULL;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void sdrm_clocks_destroy(struct sdrm_device *sdrm)
+{
+	int i;
+
+	if (!sdrm->clks)
+		return;
+
+	for (i = 0; i < sdrm->clk_count; i++) {
+		if (sdrm->clks[i]) {
+			clk_disable_unprepare(sdrm->clks[i]);
+			clk_put(sdrm->clks[i]);
+		}
+	}
+
+	kfree(sdrm->clks);
+}
+#else
+static int sdrm_clocks_init(struct sdrm_device *sdrm,
+			    struct platform_device *pdev)
+{
+	return 0;
+}
+
+static void sdrm_clocks_destroy(struct sdrm_device *sdrm)
+{
+}
+#endif
+
+#if defined CONFIG_OF && defined CONFIG_REGULATOR
+
+#define SUPPLY_SUFFIX "-supply"
+
+/*
+ * Regulator handling code.
+ *
+ * Here we handle the num-supplies and vin*-supply properties of our
+ * "simple-framebuffer" dt node. This is necessary so that we can make sure
+ * that any regulators needed by the display hardware that the bootloader
+ * set up for us (and for which it provided a simplefb dt node), stay up,
+ * for the life of the simplefb driver.
+ *
+ * When the driver unloads, we cleanly disable, and then release the
+ * regulators.
+ *
+ * We only complain about errors here, no action is taken as the most likely
+ * error can only happen due to a mismatch between the bootloader which set
+ * up simplefb, and the regulator definitions in the device tree. Chances are
+ * that there are no adverse effects, and if there are, a clean teardown of
+ * the fb probe will not help us much either. So just complain and carry on,
+ * and hope that the user actually gets a working fb at the end of things.
+ */
+static int sdrm_regulators_init(struct sdrm_device *sdrm,
+				struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct regulator *regulator;
+	int count = 0, i = 0, ret;
+	struct property *prop;
+	const char *p;
+
+	if (dev_get_platdata(&pdev->dev) || !np)
+		return 0;
+
+	/* Count the number of regulator supplies */
+	for_each_property_of_node(np, prop) {
+		p = strstr(prop->name, SUPPLY_SUFFIX);
+		if (p && p != prop->name)
+			count++;
+	}
+
+	if (!count)
+		return 0;
+
+	sdrm->regulators = devm_kcalloc(&pdev->dev, count,
+					sizeof(struct regulator *),
+					GFP_KERNEL);
+	if (!sdrm->regulators)
+		return -ENOMEM;
+
+	/* Get all the regulators */
+	for_each_property_of_node(np, prop) {
+		char name[32]; /* 32 is max size of property name */
+
+		p = strstr(prop->name, SUPPLY_SUFFIX);
+		if (!p || p == prop->name)
+			continue;
+
+		strlcpy(name, prop->name,
+			strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1);
+		regulator = devm_regulator_get_optional(&pdev->dev, name);
+		if (IS_ERR(regulator)) {
+			if (PTR_ERR(regulator) == -EPROBE_DEFER)
+				return -EPROBE_DEFER;
+			dev_err(&pdev->dev, "regulator %s not found: %ld\n",
+				name, PTR_ERR(regulator));
+			continue;
+		}
+		sdrm->regulators[i++] = regulator;
+	}
+	sdrm->regulator_count = i;
+
+	/* Enable all the regulators */
+	for (i = 0; i < sdrm->regulator_count; i++) {
+		ret = regulator_enable(sdrm->regulators[i]);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"failed to enable regulator %d: %d\n",
+				i, ret);
+			devm_regulator_put(sdrm->regulators[i]);
+			sdrm->regulators[i] = NULL;
+		}
+	}
+
+	return 0;
+}
+
+static void sdrm_regulators_destroy(struct sdrm_device *sdrm)
+{
+	int i;
+
+	if (!sdrm->regulators)
+		return;
+
+	for (i = 0; i < sdrm->regulator_count; i++)
+		if (sdrm->regulators[i])
+			regulator_disable(sdrm->regulators[i]);
+}
+#else
+static int sdrm_regulators_init(struct sdrm_device *sdrm,
+				struct platform_device *pdev)
+{
+	return 0;
+}
+
+static void sdrm_regulators_destroy(struct sdrm_device *sdrm)
+{
+}
+#endif
+
+static int parse_dt(struct platform_device *pdev,
+		    struct simplefb_platform_data *mode)
+{
+	struct device_node *np = pdev->dev.of_node;
+	const char *format;
+	int ret;
+
+	if (!np)
+		return -ENODEV;
+
+	ret = of_property_read_u32(np, "width", &mode->width);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse width property\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(np, "height", &mode->height);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse height property\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(np, "stride", &mode->stride);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse stride property\n");
+		return ret;
+	}
+
+	ret = of_property_read_string(np, "format", &format);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't parse format property\n");
+		return ret;
+	}
+	mode->format = format;
+
+	return 0;
+}
+
+static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS;
+
+static int sdrm_pdev_init(struct sdrm_device *sdrm,
+			  struct platform_device *pdev)
+{
+	struct simplefb_platform_data *mode = pdev->dev.platform_data;
+	struct simplefb_platform_data pmode;
+	struct resource *mem;
+	unsigned int depth;
+	int ret, i, bpp;
+
+	if (!mode) {
+		mode = &pmode;
+		ret = parse_dt(pdev, mode);
+		if (ret)
+			return ret;
+	}
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(sdrm->ddev->dev, "No memory resource\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(simplefb_formats); ++i) {
+		if (strcmp(mode->format, simplefb_formats[i].name))
+			continue;
+
+		sdrm->fb_sformat = &simplefb_formats[i];
+		sdrm->fb_format = simplefb_formats[i].fourcc;
+		sdrm->fb_width = mode->width;
+		sdrm->fb_height = mode->height;
+		sdrm->fb_stride = mode->stride;
+		sdrm->fb_base = mem->start;
+		sdrm->fb_size = resource_size(mem);
+		break;
+	}
+
+	if (i >= ARRAY_SIZE(simplefb_formats)) {
+		dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
+		return -ENODEV;
+	}
+
+	switch (sdrm->fb_format) {
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_XRGB2101010:
+	case DRM_FORMAT_ARGB2101010:
+		/*
+		 * You must adjust sdrm_put() whenever you add a new format
+		 * here, otherwise, blitting operations will not work.
+		 * Furthermore, include/linux/platform_data/simplefb.h needs
+		 * to be adjusted so the platform-device actually allows this
+		 * format.
+		 */
+		break;
+	default:
+		dev_err(sdrm->ddev->dev, "Unsupported format %s\n",
+			mode->format);
+		return -ENODEV;
+	}
+
+	drm_fb_get_bpp_depth(sdrm->fb_format, &depth, &bpp);
+	if (!bpp) {
+		dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
+		return -ENODEV;
+	}
+
+	if (sdrm->fb_size < sdrm->fb_stride * sdrm->fb_height) {
+		dev_err(sdrm->ddev->dev, "FB too small\n");
+		return -ENODEV;
+	} else if ((bpp + 7) / 8 * sdrm->fb_width > sdrm->fb_stride) {
+		dev_err(sdrm->ddev->dev, "Invalid stride\n");
+		return -ENODEV;
+	}
+
+	sdrm->fb_bpp = bpp;
+
+	sdrm->fb_map = ioremap_wc(sdrm->fb_base, sdrm->fb_size);
+	if (!sdrm->fb_map) {
+		dev_err(sdrm->ddev->dev, "cannot remap VMEM\n");
+		return -EIO;
+	}
+
+	DRM_DEBUG_KMS("format: %s\n", drm_get_format_name(sdrm->fb_format));
+
+	return 0;
+}
+
+static void sdrm_pdev_destroy(struct sdrm_device *sdrm)
+{
+	if (sdrm->fb_map) {
+		iounmap(sdrm->fb_map);
+		sdrm->fb_map = NULL;
+	}
+}
+
+static int sdrm_simplefb_probe(struct platform_device *pdev)
+{
+	struct sdrm_device *sdrm;
+	struct drm_device *ddev;
+	int ret;
+
+	ddev = drm_dev_alloc(&sdrm_drm_driver, &pdev->dev);
+	if (!ddev)
+		return -ENOMEM;
+
+	sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
+	if (!sdrm)
+		goto err_free;
+
+	ddev->dev_private = sdrm;
+	sdrm->ddev = ddev;
+
+	ret = sdrm_pdev_init(sdrm, pdev);
+	if (ret)
+		goto err_free;
+
+	ret = sdrm_drm_modeset_init(sdrm);
+	if (ret)
+		goto err_destroy;
+
+	ret = sdrm_clocks_init(sdrm, pdev);
+	if (ret < 0)
+		goto err_cleanup;
+
+	ret = sdrm_regulators_init(sdrm, pdev);
+	if (ret < 0)
+		goto err_clocks;
+
+	platform_set_drvdata(pdev, ddev);
+	ret = drm_dev_register(ddev, 0);
+	if (ret)
+		goto err_regulators;
+
+	DRM_INFO("Initialized %s on minor %d\n", ddev->driver->name,
+		 ddev->primary->index);
+
+	return 0;
+
+err_regulators:
+	sdrm_regulators_destroy(sdrm);
+err_clocks:
+	sdrm_clocks_destroy(sdrm);
+err_cleanup:
+	drm_mode_config_cleanup(ddev);
+err_destroy:
+	sdrm_pdev_destroy(sdrm);
+err_free:
+	drm_dev_unref(ddev);
+	kfree(sdrm);
+
+	return ret;
+}
+
+static int sdrm_simplefb_remove(struct platform_device *pdev)
+{
+	struct drm_device *ddev = platform_get_drvdata(pdev);
+	struct sdrm_device *sdrm = ddev->dev_private;
+
+	drm_dev_unregister(ddev);
+	drm_mode_config_cleanup(ddev);
+
+	/* protect fb_map removal against sdrm_blit() */
+	drm_modeset_lock_all(ddev);
+	sdrm_pdev_destroy(sdrm);
+	drm_modeset_unlock_all(ddev);
+
+	sdrm_regulators_destroy(sdrm);
+	sdrm_clocks_destroy(sdrm);
+
+	drm_dev_unref(ddev);
+	kfree(sdrm);
+
+	return 0;
+}
+
+static const struct of_device_id simplefb_of_match[] = {
+	{ .compatible = "simple-framebuffer", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, simplefb_of_match);
+
+static struct platform_driver sdrm_simplefb_driver = {
+	.probe = sdrm_simplefb_probe,
+	.remove = sdrm_simplefb_remove,
+	.driver = {
+		.name = "simple-framebuffer",
+		.mod_name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+		.of_match_table = simplefb_of_match,
+	},
+};
+
+static int __init sdrm_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&sdrm_simplefb_driver);
+	if (ret)
+		return ret;
+
+	if (IS_ENABLED(CONFIG_OF_ADDRESS) && of_chosen) {
+		struct device_node *np;
+
+		for_each_child_of_node(of_chosen, np) {
+			if (of_device_is_compatible(np, "simple-framebuffer"))
+				of_platform_device_create(np, NULL, NULL);
+		}
+	}
+
+	return 0;
+}
+module_init(sdrm_init);
+
+static void __exit sdrm_exit(void)
+{
+	platform_driver_unregister(&sdrm_simplefb_driver);
+}
+module_exit(sdrm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
+MODULE_ALIAS("platform:simple-framebuffer");
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
new file mode 100644
index 0000000..8cced80
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
@@ -0,0 +1,202 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * 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 <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include "simpledrm.h"
+
+struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
+					      size_t size)
+{
+	struct sdrm_gem_object *obj;
+
+	WARN_ON(!size || (size & ~PAGE_MASK) != 0);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	if (drm_gem_object_init(ddev, &obj->base, size)) {
+		kfree(obj);
+		return NULL;
+	}
+
+	return obj;
+}
+
+int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
+		     struct drm_mode_create_dumb *args)
+{
+	struct sdrm_gem_object *obj;
+	int r;
+
+	/* overflow checks are done by DRM core */
+	args->pitch = (args->bpp + 7) / 8 * args->width;
+	args->size = PAGE_ALIGN(args->pitch * args->height);
+
+	obj = sdrm_gem_alloc_object(ddev, args->size);
+	if (!obj)
+		return -ENOMEM;
+
+	r = drm_gem_handle_create(dfile, &obj->base, &args->handle);
+	if (r) {
+		drm_gem_object_unreference_unlocked(&obj->base);
+		return r;
+	}
+
+	/* handle owns a reference */
+	drm_gem_object_unreference_unlocked(&obj->base);
+
+	return 0;
+}
+
+int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	int ret;
+
+	ret = drm_gem_mmap(filp, vma);
+	if (ret)
+		return ret;
+
+	vma->vm_flags &= ~VM_PFNMAP;
+	vma->vm_flags |= VM_MIXEDMAP;
+	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
+
+	return 0;
+}
+
+int sdrm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct drm_gem_object *gobj = vma->vm_private_data;
+	struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
+	pgoff_t offset;
+	int ret;
+
+	if (!obj->pages)
+		return VM_FAULT_SIGBUS;
+
+	offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >>
+		 PAGE_SHIFT;
+
+	ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address,
+			     obj->pages[offset]);
+	switch (ret) {
+	case -EAGAIN:
+	case 0:
+	case -ERESTARTSYS:
+	case -EINTR:
+	case -EBUSY:
+		return VM_FAULT_NOPAGE;
+
+	case -ENOMEM:
+		return VM_FAULT_OOM;
+	}
+
+	return VM_FAULT_SIGBUS;
+}
+
+int sdrm_gem_get_pages(struct sdrm_gem_object *obj)
+{
+	struct page **pages;
+
+	if (obj->pages)
+		return 0;
+
+	pages = drm_gem_get_pages(&obj->base);
+	if (IS_ERR(pages))
+		return PTR_ERR(pages);
+
+	obj->pages = pages;
+
+	return 0;
+}
+
+static void sdrm_gem_put_pages(struct sdrm_gem_object *obj)
+{
+	if (!obj->pages)
+		return;
+
+	drm_gem_put_pages(&obj->base, obj->pages, false, false);
+	obj->pages = NULL;
+}
+
+int sdrm_gem_vmap(struct sdrm_gem_object *obj)
+{
+	int page_count = obj->base.size / PAGE_SIZE;
+	int ret;
+
+	if (obj->vmapping)
+		return 0;
+
+	ret = sdrm_gem_get_pages(obj);
+	if (ret)
+		return ret;
+
+	obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
+	if (!obj->vmapping)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void sdrm_gem_vunmap(struct sdrm_gem_object *obj)
+{
+	vunmap(obj->vmapping);
+	obj->vmapping = NULL;
+
+	sdrm_gem_put_pages(obj);
+}
+
+void sdrm_gem_free_object(struct drm_gem_object *gobj)
+{
+	struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
+
+	if (obj->vmapping)
+		sdrm_gem_vunmap(obj);
+
+	if (obj->pages)
+		sdrm_gem_put_pages(obj);
+
+	drm_gem_object_release(gobj);
+	kfree(obj);
+}
+
+int sdrm_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
+			 uint32_t handle, uint64_t *offset)
+{
+	struct sdrm_gem_object *obj;
+	struct drm_gem_object *gobj;
+	int ret;
+
+	gobj = drm_gem_object_lookup(dfile, handle);
+	if (!gobj)
+		return -ENOENT;
+
+	obj = to_sdrm_bo(gobj);
+
+	ret = sdrm_gem_get_pages(obj);
+	if (ret)
+		goto out_unref;
+
+	ret = drm_gem_create_mmap_offset(gobj);
+	if (ret)
+		goto out_unref;
+
+	*offset = drm_vma_node_offset_addr(&gobj->vma_node);
+
+out_unref:
+	drm_gem_object_unreference_unlocked(gobj);
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
new file mode 100644
index 0000000..e6dc3df
--- /dev/null
+++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
@@ -0,0 +1,234 @@
+/*
+ * SimpleDRM firmware framebuffer driver
+ * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * 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_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <linux/slab.h>
+
+#include "simpledrm.h"
+
+static const uint32_t sdrm_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+};
+
+static int sdrm_conn_get_modes(struct drm_connector *conn)
+{
+	struct sdrm_device *sdrm = conn->dev->dev_private;
+	struct drm_display_mode *mode;
+
+	mode = drm_cvt_mode(sdrm->ddev, sdrm->fb_width, sdrm->fb_height,
+			    60, false, false, false);
+	if (!mode)
+		return 0;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(conn, mode);
+
+	return 1;
+}
+
+static const struct drm_connector_helper_funcs sdrm_conn_hfuncs = {
+	.get_modes = sdrm_conn_get_modes,
+	.best_encoder = drm_atomic_helper_best_encoder,
+};
+
+static enum drm_connector_status sdrm_conn_detect(struct drm_connector *conn,
+						  bool force)
+{
+	/*
+	 * We simulate an always connected monitor. simple-fb doesn't
+	 * provide any way to detect whether the connector is active. Hence,
+	 * signal DRM core that it is always connected.
+	 */
+
+	return connector_status_connected;
+}
+
+static const struct drm_connector_funcs sdrm_conn_ops = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = sdrm_conn_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static inline struct sdrm_device *
+pipe_to_sdrm(struct drm_simple_display_pipe *pipe)
+{
+	return container_of(pipe, struct sdrm_device, pipe);
+}
+
+static void sdrm_crtc_send_vblank_event(struct drm_crtc *crtc)
+{
+	if (crtc->state && crtc->state->event) {
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+		crtc->state->event = NULL;
+	}
+}
+
+void sdrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+			      struct drm_plane_state *plane_state)
+{
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct sdrm_device *sdrm = pipe_to_sdrm(pipe);
+
+	sdrm_crtc_send_vblank_event(&pipe->crtc);
+
+	if (fb) {
+		pipe->plane.fb = fb;
+		sdrm_dirty_all_locked(sdrm);
+	}
+}
+
+static void sdrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
+				     struct drm_crtc_state *crtc_state)
+{
+	sdrm_crtc_send_vblank_event(&pipe->crtc);
+}
+
+static void sdrm_display_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	sdrm_crtc_send_vblank_event(&pipe->crtc);
+}
+
+static const struct drm_simple_display_pipe_funcs sdrm_pipe_funcs = {
+	.update = sdrm_display_pipe_update,
+	.enable = sdrm_display_pipe_enable,
+	.disable = sdrm_display_pipe_disable,
+};
+
+static int sdrm_fb_create_handle(struct drm_framebuffer *fb,
+				 struct drm_file *dfile,
+				 unsigned int *handle)
+{
+	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+
+	return drm_gem_handle_create(dfile, &sfb->obj->base, handle);
+}
+
+static void sdrm_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
+
+	drm_framebuffer_cleanup(fb);
+	drm_gem_object_unreference_unlocked(&sfb->obj->base);
+	kfree(sfb);
+}
+
+static const struct drm_framebuffer_funcs sdrm_fb_ops = {
+	.create_handle = sdrm_fb_create_handle,
+	.dirty = sdrm_dirty,
+	.destroy = sdrm_fb_destroy,
+};
+
+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)
+{
+	fb->obj = obj;
+	drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd);
+
+	return drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops);
+}
+
+static struct drm_framebuffer *sdrm_fb_create(struct drm_device *ddev,
+					      struct drm_file *dfile,
+					      const struct drm_mode_fb_cmd2 *cmd)
+{
+	struct sdrm_framebuffer *fb;
+	struct drm_gem_object *gobj;
+	int ret;
+
+	if (cmd->flags)
+		return ERR_PTR(-EINVAL);
+
+	gobj = drm_gem_object_lookup(dfile, cmd->handles[0]);
+	if (!gobj)
+		return ERR_PTR(-EINVAL);
+
+	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+	if (!fb) {
+		ret = -ENOMEM;
+		goto err_unref;
+	}
+
+	ret = sdrm_fb_init(ddev, fb, cmd, to_sdrm_bo(gobj));
+	if (ret)
+		goto err_free;
+
+	ret = sdrm_gem_vmap(fb->obj);
+	if (ret)
+		goto err_free;
+
+	DRM_DEBUG_KMS("[FB:%d] pixel_format: %s\n", fb->base.base.id,
+		      drm_get_format_name(fb->base.pixel_format));
+
+	return &fb->base;
+
+err_free:
+	kfree(fb);
+err_unref:
+	drm_gem_object_unreference_unlocked(gobj);
+
+	return ERR_PTR(ret);
+}
+
+static const struct drm_mode_config_funcs sdrm_mode_config_ops = {
+	.fb_create = sdrm_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+int sdrm_drm_modeset_init(struct sdrm_device *sdrm)
+{
+	struct drm_connector *conn = &sdrm->conn;
+	struct drm_device *ddev = sdrm->ddev;
+	int ret;
+
+	drm_mode_config_init(ddev);
+	ddev->mode_config.min_width = sdrm->fb_width;
+	ddev->mode_config.max_width = sdrm->fb_width;
+	ddev->mode_config.min_height = sdrm->fb_height;
+	ddev->mode_config.max_height = sdrm->fb_height;
+	ddev->mode_config.preferred_depth = sdrm->fb_bpp;
+	ddev->mode_config.funcs = &sdrm_mode_config_ops;
+
+	drm_connector_helper_add(conn, &sdrm_conn_hfuncs);
+	ret = drm_connector_init(ddev, conn, &sdrm_conn_ops,
+				 DRM_MODE_CONNECTOR_VIRTUAL);
+	if (ret)
+		goto err_cleanup;
+
+	ret = drm_simple_display_pipe_init(ddev, &sdrm->pipe, &sdrm_pipe_funcs,
+					   sdrm_formats,
+					   ARRAY_SIZE(sdrm_formats), conn);
+	if (ret)
+		goto err_cleanup;
+
+	drm_mode_config_reset(ddev);
+
+	return 0;
+
+err_cleanup:
+	drm_mode_config_cleanup(ddev);
+
+	return ret;
+}
--
2.8.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v4 4/5] drm: simpledrm: add fbdev fallback support
  2016-08-22 20:25 [PATCH v4 0/5] drm: add SimpleDRM driver Noralf Trønnes
                   ` (2 preceding siblings ...)
  2016-08-22 20:25 ` [PATCH v4 3/5] drm: add SimpleDRM driver Noralf Trønnes
@ 2016-08-22 20:25 ` Noralf Trønnes
  2016-08-23  6:10   ` Daniel Vetter
  2016-08-22 20:25 ` [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers() Noralf Trønnes
  4 siblings, 1 reply; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-22 20:25 UTC (permalink / raw)
  To: dri-devel
  Cc: robh, devicetree, dh.herrmann, linux-kernel, Noralf Trønnes

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@gmail.com
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---

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@gmail.com>
+ *
+ * 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

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers()
  2016-08-22 20:25 [PATCH v4 0/5] drm: add SimpleDRM driver Noralf Trønnes
                   ` (3 preceding siblings ...)
  2016-08-22 20:25 ` [PATCH v4 4/5] drm: simpledrm: add fbdev fallback support Noralf Trønnes
@ 2016-08-22 20:25 ` Noralf Trønnes
  2016-08-23 12:41   ` Daniel Vetter
  4 siblings, 1 reply; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-22 20:25 UTC (permalink / raw)
  To: dri-devel
  Cc: robh, devicetree, dh.herrmann, linux-kernel, Noralf Trønnes

There is currently no non-fbdev mechanism in place to kick out
simpledrm when the real hw-driver is probed. As a stop gap until
that is in place, honour remove_conflicting_framebuffers() and
delete the simple-framebuffer platform device when it's called.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---

Changes from version 3:
- drm_device.platformdev is deprecated, use to_platform_device(ddev->dev).
- fb_helper might have been released in sdrm_fbdev_fb_destroy(),
  so open code drm_fb_helper_release_fbi()
- Strengthen the test in sdrm_fbdev_event_notify() that we're the one.

Changes from version 2:
- Don't forget to free fb_info when kicked out.

 drivers/gpu/drm/simpledrm/Kconfig           |  5 +++
 drivers/gpu/drm/simpledrm/simpledrm.h       |  2 +
 drivers/gpu/drm/simpledrm/simpledrm_drv.c   |  3 ++
 drivers/gpu/drm/simpledrm/simpledrm_fbdev.c | 62 ++++++++++++++++++++++++++++-
 4 files changed, 70 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
index 3257590..4275d13 100644
--- a/drivers/gpu/drm/simpledrm/Kconfig
+++ b/drivers/gpu/drm/simpledrm/Kconfig
@@ -16,6 +16,11 @@ config DRM_SIMPLEDRM
 	  If fbdev support is enabled, this driver will also provide an fbdev
 	  compatibility layer that supports fbcon, mmap is not supported.

+	  WARNING
+	  fbdev must be enabled for simpledrm to disable itself when a real
+	  hw-driver is probed. It relies on remove_conflicting_framebuffers()
+	  to be called by the hw-driver.
+
 	  If unsure, say Y.

 	  To compile this driver as a module, choose M here: the
diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
index d4eb52c..3cca196 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm.h
+++ b/drivers/gpu/drm/simpledrm/simpledrm.h
@@ -87,5 +87,7 @@ int sdrm_fb_init(struct drm_device *ddev, struct sdrm_framebuffer *fb,
 		 struct sdrm_gem_object *obj);
 void sdrm_fbdev_init(struct sdrm_device *sdrm);
 void sdrm_fbdev_cleanup(struct sdrm_device *sdrm);
+void sdrm_fbdev_kickout_init(void);
+void sdrm_fbdev_kickout_exit(void);

 #endif /* SDRM_DRV_H */
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
index fe752c6..0750652 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_drv.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
@@ -531,12 +531,15 @@ static int __init sdrm_init(void)
 		}
 	}

+	sdrm_fbdev_kickout_init();
+
 	return 0;
 }
 module_init(sdrm_init);

 static void __exit sdrm_exit(void)
 {
+	sdrm_fbdev_kickout_exit();
 	platform_driver_unregister(&sdrm_simplefb_driver);
 }
 module_exit(sdrm_exit);
diff --git a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
index c6596ad..7c6db2c 100644
--- a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
+++ b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
@@ -44,6 +44,20 @@ static int sdrm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
 	return -ENODEV;
 }

+/*
+ * Releasing has to be done outside the notifier callchain when we're
+ * kicked out, since do_unregister_framebuffer() calls put_fb_info()
+ * after the notifier has run.
+ * Open code drm_fb_helper_release_fbi() since fb_helper is freed at
+ * this point when kicked out.
+ */
+static void sdrm_fbdev_fb_destroy(struct fb_info *info)
+{
+	if (info->cmap.len)
+		fb_dealloc_cmap(&info->cmap);
+	framebuffer_release(info);
+}
+
 static struct fb_ops sdrm_fbdev_ops = {
 	.owner		= THIS_MODULE,
 	.fb_fillrect	= drm_fb_helper_sys_fillrect,
@@ -53,6 +67,7 @@ static struct fb_ops sdrm_fbdev_ops = {
 	.fb_set_par	= drm_fb_helper_set_par,
 	.fb_setcmap	= drm_fb_helper_setcmap,
 	.fb_mmap	= sdrm_fb_mmap,
+	.fb_destroy	= sdrm_fbdev_fb_destroy,
 };

 static int sdrm_fbdev_create(struct drm_fb_helper *helper,
@@ -110,6 +125,9 @@ static int sdrm_fbdev_create(struct drm_fb_helper *helper,
 	fbi->screen_base = obj->vmapping;
 	fbi->fix.smem_len = sdrm->fb_size;

+	fbi->apertures->ranges[0].base = sdrm->fb_base;
+	fbi->apertures->ranges[0].size = sdrm->fb_size;
+
 	return 0;

 err_fbi_release:
@@ -188,9 +206,13 @@ void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
 	sdrm->fb_helper = NULL;
 	fbdev = to_sdrm_fbdev(fb_helper);

-	drm_fb_helper_unregister_fbi(fb_helper);
+	/* it might have been kicked out */
+	if (registered_fb[fbdev->fb_helper.fbdev->node])
+		drm_fb_helper_unregister_fbi(fb_helper);
+
+	/* freeing fb_info is done in fb_ops.fb_destroy() */
+
 	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);
@@ -199,3 +221,39 @@ void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
 	drm_fb_helper_fini(fb_helper);
 	kfree(fbdev);
 }
+
+static int sdrm_fbdev_event_notify(struct notifier_block *self,
+				   unsigned long action, void *data)
+{
+	struct sdrm_device *sdrm;
+	struct fb_event *event = data;
+	struct fb_info *info = event->info;
+	struct drm_fb_helper *fb_helper = info->par;
+
+	if (action != FB_EVENT_FB_UNREGISTERED)
+		return NOTIFY_DONE;
+
+	if (!fb_helper || !fb_helper->dev || fb_helper->fbdev != info)
+		return NOTIFY_DONE;
+
+	sdrm = fb_helper->dev->dev_private;
+
+	if (sdrm && sdrm->fb_helper == fb_helper)
+		platform_device_del(to_platform_device(fb_helper->dev->dev));
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block sdrm_fbdev_event_notifier = {
+	.notifier_call  = sdrm_fbdev_event_notify,
+};
+
+void sdrm_fbdev_kickout_init(void)
+{
+	fb_register_client(&sdrm_fbdev_event_notifier);
+}
+
+void sdrm_fbdev_kickout_exit(void)
+{
+	fb_unregister_client(&sdrm_fbdev_event_notifier);
+}
--
2.8.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 2/5] drm/fb-helper: Add drm_fb_helper_set_suspend_lock()
  2016-08-22 20:25 ` [PATCH v4 2/5] drm/fb-helper: Add drm_fb_helper_set_suspend_lock() Noralf Trønnes
@ 2016-08-23  6:07   ` Daniel Vetter
  0 siblings, 0 replies; 19+ messages in thread
From: Daniel Vetter @ 2016-08-23  6:07 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-kernel, devicetree

On Mon, Aug 22, 2016 at 10:25:22PM +0200, Noralf Trønnes wrote:
> This adds a function that also takes the console lock before calling
> fb_set_suspend() in contrast to drm_fb_helper_set_suspend() which is
> a plain wrapper around fb_set_suspend().
> Resume is run asynchronously using a worker if the console lock is
> already taken. This is modelled after the i915 driver.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/gpu/drm/drm_fb_helper.c | 57 +++++++++++++++++++++++++++++++++++++++++
>  include/drm/drm_fb_helper.h     |  9 +++++++
>  2 files changed, 66 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index ce54e98..897e6f4 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -29,6 +29,7 @@
>   */
>  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>  
> +#include <linux/console.h>
>  #include <linux/kernel.h>
>  #include <linux/sysrq.h>
>  #include <linux/slab.h>
> @@ -618,6 +619,16 @@ static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
>  	kfree(helper->crtc_info);
>  }
>  
> +static void drm_fb_helper_resume_worker(struct work_struct *work)
> +{
> +	struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper,
> +						    resume_work);
> +
> +	console_lock();
> +	fb_set_suspend(helper->fbdev, 0);
> +	console_unlock();
> +}
> +
>  static void drm_fb_helper_dirty_work(struct work_struct *work)
>  {
>  	struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper,
> @@ -649,6 +660,7 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
>  {
>  	INIT_LIST_HEAD(&helper->kernel_fb_list);
>  	spin_lock_init(&helper->dirty_lock);
> +	INIT_WORK(&helper->resume_work, drm_fb_helper_resume_worker);
>  	INIT_WORK(&helper->dirty_work, drm_fb_helper_dirty_work);
>  	helper->dirty_clip.x1 = helper->dirty_clip.y1 = ~0;
>  	helper->funcs = funcs;
> @@ -1035,6 +1047,51 @@ void drm_fb_helper_set_suspend(struct drm_fb_helper *fb_helper, int state)
>  }
>  EXPORT_SYMBOL(drm_fb_helper_set_suspend);
>  
> +/**
> + * drm_fb_helper_set_suspend_lock - wrapper around fb_set_suspend that also
> + *                                  takes the console lock
> + * @fb_helper: driver-allocated fbdev helper
> + * @state: desired state, zero to resume, non-zero to suspend
> + *
> + * A wrapper around fb_set_suspend() that takes the console lock. If the lock
> + * isn't available on resume, a worker is tasked with waiting for the lock
> + * to become available. The console lock can be pretty contented on resume
> + * due to all the printk activity.
> + *
> + * This function can be called multiple times with the same state since
> + * &fb_info->state is checked to see if fbdev is running or not before locking.
> + *
> + * Use drm_fb_helper_set_suspend() if you need to take the lock yourself.
> + */
> +void drm_fb_helper_set_suspend_lock(struct drm_fb_helper *fb_helper, int state)

We generally call the functions which take the locks themselves _unlocked
(or the version that requires locks to be held _locked). Other nit: I
think added a sentence to the kerneldoc of drm_fb_helper_set_suspend to
instead use this one (since it avoids lock contention on resume) would be
good.

Otherwise looks great.

And if you're bored, following up with a bunch of patches to roll this out
to drivers would be awesome ;-)

> +{
> +	if (!fb_helper || !fb_helper->fbdev)
> +		return;
> +
> +	/* make sure there's no pending/ongoing resume */
> +	flush_work(&fb_helper->resume_work);
> +
> +	if (state) { /* suspend */

Hm, one more nit: rename state to suspend and you both make the
declaration more self-explanatory, and can drop these 2 comments here.

Thanks, Daniel

> +		if (fb_helper->fbdev->state != FBINFO_STATE_RUNNING)
> +			return;
> +
> +		console_lock();
> +
> +	} else { /* resume */
> +		if (fb_helper->fbdev->state == FBINFO_STATE_RUNNING)
> +			return;
> +
> +		if (!console_trylock()) {
> +			schedule_work(&fb_helper->resume_work);
> +			return;
> +		}
> +	}
> +
> +	fb_set_suspend(fb_helper->fbdev, state);
> +	console_unlock();
> +}
> +EXPORT_SYMBOL(drm_fb_helper_set_suspend_lock);
> +
>  static int setcolreg(struct drm_crtc *crtc, u16 red, u16 green,
>  		     u16 blue, u16 regno, struct fb_info *info)
>  {
> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> index db8d478..db7e861 100644
> --- a/include/drm/drm_fb_helper.h
> +++ b/include/drm/drm_fb_helper.h
> @@ -176,6 +176,7 @@ struct drm_fb_helper_connector {
>   *              the screen buffer
>   * @dirty_lock: spinlock protecting @dirty_clip
>   * @dirty_work: worker used to flush the framebuffer
> + * @resume_work: worker used during resume if the console lock is already taken
>   *
>   * This is the main structure used by the fbdev helpers. Drivers supporting
>   * fbdev emulation should embedded this into their overall driver structure.
> @@ -196,6 +197,7 @@ struct drm_fb_helper {
>  	struct drm_clip_rect dirty_clip;
>  	spinlock_t dirty_lock;
>  	struct work_struct dirty_work;
> +	struct work_struct resume_work;
>  
>  	/**
>  	 * @kernel_fb_list:
> @@ -264,6 +266,8 @@ void drm_fb_helper_cfb_imageblit(struct fb_info *info,
>  				 const struct fb_image *image);
>  
>  void drm_fb_helper_set_suspend(struct drm_fb_helper *fb_helper, int state);
> +void drm_fb_helper_set_suspend_lock(struct drm_fb_helper *fb_helper,
> +				    int state);
>  
>  int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info);
>  
> @@ -421,6 +425,11 @@ static inline void drm_fb_helper_set_suspend(struct drm_fb_helper *fb_helper,
>  {
>  }
>  
> +static inline void
> +drm_fb_helper_set_suspend_lock(struct drm_fb_helper *fb_helper, int state)
> +{
> +}
> +
>  static inline int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
>  {
>  	return 0;
> -- 
> 2.8.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 4/5] drm: simpledrm: add fbdev fallback support
  2016-08-22 20:25 ` [PATCH v4 4/5] drm: simpledrm: add fbdev fallback support Noralf Trønnes
@ 2016-08-23  6:10   ` Daniel Vetter
  0 siblings, 0 replies; 19+ messages in thread
From: Daniel Vetter @ 2016-08-23  6:10 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-kernel, devicetree

On Mon, Aug 22, 2016 at 10:25:24PM +0200, Noralf Trønnes wrote:
> 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@gmail.com
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
> 
> 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@gmail.com>
> + *
> + * 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);

David's original fbdev support needed the set_suspend here because without
it fbcon would have trampled all over native kms clients. But with the new
implementation using the fb emulation helpers and dirty callbacks we don't
need that any more I think. I think for consistency with other drivers
it'd be good to remove this flag.

Otherwise this looks good now I think.
-Daniel

> +	}
> +}
> +
>  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
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-08-22 20:25 ` [PATCH v4 3/5] drm: add SimpleDRM driver Noralf Trønnes
@ 2016-08-23  6:17   ` Daniel Vetter
  2016-08-25 22:11     ` Noralf Trønnes
  2016-08-25 13:09   ` Rob Herring
  2016-09-01 23:48   ` David Herrmann
  2 siblings, 1 reply; 19+ messages in thread
From: Daniel Vetter @ 2016-08-23  6:17 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, devicetree, linux-kernel

On Mon, Aug 22, 2016 at 10:25:23PM +0200, Noralf Trønnes wrote:
> The SimpleDRM driver binds to simple-framebuffer devices and provides a
> DRM/KMS API. It provides only a single CRTC+encoder+connector combination
> plus one initial mode.
> 
> Userspace can create dumb-buffers which can be blit into the real
> framebuffer similar to UDL. No access to the real framebuffer is allowed
> (compared to earlier version of this driver) to avoid security issues.
> Furthermore, this way we can support arbitrary modes as long as we have a
> conversion-helper.
> 
> The driver was originally written by David Herrmann in 2014.
> My main contribution is to make use of drm_simple_kms_helper and
> rework the probe path to avoid use of the deprecated drm_platform_init()
> and drm_driver.{load,unload}().
> Additions have also been made for later changes to the Device Tree binding
> document, like support for clocks, regulators and having the node under
> /chosen. This code was lifted verbatim from simplefb.c.
> 
> Cc: dh.herrmann@gmail.com
> Cc: libv@skynet.be
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
> 
> Changes from version 3:
> - Reworked gem code to match udl
> - Dropped PRIME support
> - Dropped dirty_info_property, it's gone
> - Don't use drm_device.platformdev it's deprecated
> - Remove struct sdrm_device #ifdef's
> - Split out sdrm_fb_init() from sdrm_fb_create(), needed by fbdev code
> - Simplify drm_clip validation by extending the check in
>   sdrm_dirty() and drop the one in sdrm_blit()
> - Removed sdrm_dirty_all_unlocked() which was unused.
> 
> Changes from version 2:
> - Remove superfluos module.h includes
> - Move includes from header to source files
> - Set plane.fb before flushing in pipe update, or else the previous one
>   gets flushed
> - Added check for vblank event in sdrm_display_pipe_update()
> 
> Changes from version 1:
> - Move platform_set_drvdata() before drm_dev_register()
> - Remove drm_legacy_mmap() call.
> - Set mode_config.{min,max}_{width,height} to the actual dimensions
>   of the native framebuffer
> - Remove plane positioning since it won't work with the simple display pipe,
>   meaning sdrm_display_pipe_check() isn't necessary either
> - Support the additions to the Device Tree binding document, including
>   clocks, regulators and having the node under /chosen
> 
> Changes from previous version:
> - Remove FB_SIMPLE=n dependency to avoid kconfig recursive error
> - Changed module name to match kconfig help text: sdrm -> simpledrm
> - Use drm_simple_display_pipe
> - Replace deprecated drm_platform_init()
> - sdrm_dumb_create(): drm_gem_object_unreference() -> *_unlocked()
> - sdrm_dumb_map_offset(): drm_gem_object_lookup() remove drm_device parameter
> - sdrm_drm_mmap() changes:
>   Remove struct_mutex locking
>   Add drm_vma_offset_{lock,unlock}_lookup()
>   drm_mmap() -> drm_legacy_mmap()
> - dma_buf_begin_cpu_access() doesn't require start and length anymore
> - Use drm_cvt_mode() instead of open coding a mode
> - Fix format conversion. In the intermediate step, store the 8/6/5 bit color
>   value in the upper part of the 16-bit color variable, not the lower.
> - Support clips == NULL in sdrm_dirty()
> - Set mode_config.preferred_depth
> - Attach mode_config.dirty_info_property to connector
> 
>  drivers/gpu/drm/Kconfig                      |   2 +
>  drivers/gpu/drm/Makefile                     |   1 +
>  drivers/gpu/drm/simpledrm/Kconfig            |  19 +
>  drivers/gpu/drm/simpledrm/Makefile           |   4 +
>  drivers/gpu/drm/simpledrm/simpledrm.h        |  86 +++++
>  drivers/gpu/drm/simpledrm/simpledrm_damage.c | 235 ++++++++++++
>  drivers/gpu/drm/simpledrm/simpledrm_drv.c    | 543 +++++++++++++++++++++++++++
>  drivers/gpu/drm/simpledrm/simpledrm_gem.c    | 202 ++++++++++
>  drivers/gpu/drm/simpledrm/simpledrm_kms.c    | 234 ++++++++++++
>  9 files changed, 1326 insertions(+)
>  create mode 100644 drivers/gpu/drm/simpledrm/Kconfig
>  create mode 100644 drivers/gpu/drm/simpledrm/Makefile
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm.h
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_damage.c
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_drv.c
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_gem.c
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_kms.c
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index fc35731..a54cc8d 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -290,3 +290,5 @@ source "drivers/gpu/drm/arc/Kconfig"
>  source "drivers/gpu/drm/hisilicon/Kconfig"
> 
>  source "drivers/gpu/drm/mediatek/Kconfig"
> +
> +source "drivers/gpu/drm/simpledrm/Kconfig"
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index e3dba6f..eba32ad 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -83,3 +83,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
>  obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
>  obj-$(CONFIG_DRM_ARCPGU)+= arc/
>  obj-y			+= hisilicon/
> +obj-$(CONFIG_DRM_SIMPLEDRM) += simpledrm/
> diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
> new file mode 100644
> index 0000000..f45b25d
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/Kconfig
> @@ -0,0 +1,19 @@
> +config DRM_SIMPLEDRM
> +	tristate "Simple firmware framebuffer DRM driver"
> +	depends on DRM
> +	select DRM_KMS_HELPER
> +	help
> +	  SimpleDRM can run on all systems with pre-initialized graphics
> +	  hardware. It uses a framebuffer that was initialized during
> +	  firmware boot. No page-flipping, modesetting or other advanced
> +	  features are available. However, other DRM drivers can be loaded
> +	  later and take over from SimpleDRM if they provide real hardware
> +	  support.
> +
> +	  SimpleDRM supports "simple-framebuffer" DeviceTree objects and
> +	  compatible platform framebuffers.
> +
> +	  If unsure, say Y.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called simpledrm.
> diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
> new file mode 100644
> index 0000000..f6a62dc
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/Makefile
> @@ -0,0 +1,4 @@
> +simpledrm-y :=	simpledrm_drv.o simpledrm_kms.o simpledrm_gem.o \
> +		simpledrm_damage.o
> +
> +obj-$(CONFIG_DRM_SIMPLEDRM) := simpledrm.o
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
> new file mode 100644
> index 0000000..0739581
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm.h
> @@ -0,0 +1,86 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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.
> + */
> +
> +#ifndef SDRM_DRV_H
> +#define SDRM_DRV_H
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_gem.h>
> +#include <drm/drm_simple_kms_helper.h>
> +
> +struct simplefb_format;
> +struct regulator;
> +struct clk;
> +
> +struct sdrm_device {
> +	struct drm_device *ddev;
> +	struct drm_simple_display_pipe pipe;
> +	struct drm_connector conn;
> +
> +	/* framebuffer information */
> +	const struct simplefb_format *fb_sformat;
> +	u32 fb_format;
> +	u32 fb_width;
> +	u32 fb_height;
> +	u32 fb_stride;
> +	u32 fb_bpp;
> +	unsigned long fb_base;
> +	unsigned long fb_size;
> +	void *fb_map;
> +
> +	unsigned int clk_count;
> +	struct clk **clks;
> +
> +	u32 regulator_count;
> +	struct regulator **regulators;
> +};
> +
> +int sdrm_drm_modeset_init(struct sdrm_device *sdrm);
> +
> +int sdrm_dirty(struct drm_framebuffer *fb,
> +	       struct drm_file *file,
> +	       unsigned int flags, unsigned int color,
> +	       struct drm_clip_rect *clips,
> +	       unsigned int num_clips);
> +int sdrm_dirty_all_locked(struct sdrm_device *sdrm);
> +
> +struct sdrm_gem_object {
> +	struct drm_gem_object base;
> +	struct page **pages;
> +	void *vmapping;
> +};
> +
> +#define to_sdrm_bo(x) container_of(x, struct sdrm_gem_object, base)
> +
> +int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma);
> +int sdrm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
> +int sdrm_gem_vmap(struct sdrm_gem_object *sobj);
> +
> +struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
> +					      size_t size);
> +void sdrm_gem_free_object(struct drm_gem_object *obj);
> +
> +int sdrm_dumb_create(struct drm_file *file_priv, struct drm_device *ddev,
> +		     struct drm_mode_create_dumb *arg);
> +int sdrm_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev,
> +			 uint32_t handle, uint64_t *offset);
> +
> +struct sdrm_framebuffer {
> +	struct drm_framebuffer base;
> +	struct sdrm_gem_object *obj;
> +};
> +
> +#define to_sdrm_fb(x) container_of(x, struct sdrm_framebuffer, base)
> +
> +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);
> +
> +#endif /* SDRM_DRV_H */
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_damage.c b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
> new file mode 100644
> index 0000000..52c845f
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
> @@ -0,0 +1,235 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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 <asm/unaligned.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc.h>
> +#include <linux/dma-buf.h>
> +#include <linux/kernel.h>
> +#include <linux/string.h>
> +
> +#include "simpledrm.h"
> +
> +static inline void sdrm_put(u8 *dst, u32 four_cc, u16 r, u16 g, u16 b)
> +{
> +	switch (four_cc) {
> +	case DRM_FORMAT_RGB565:
> +		r >>= 11;
> +		g >>= 10;
> +		b >>= 11;
> +		put_unaligned((u16)((r << 11) | (g << 5) | b), (u16 *)dst);
> +		break;
> +	case DRM_FORMAT_XRGB1555:
> +	case DRM_FORMAT_ARGB1555:
> +		r >>= 11;
> +		g >>= 11;
> +		b >>= 11;
> +		put_unaligned((u16)((r << 10) | (g << 5) | b), (u16 *)dst);
> +		break;
> +	case DRM_FORMAT_RGB888:
> +		r >>= 8;
> +		g >>= 8;
> +		b >>= 8;
> +#ifdef __LITTLE_ENDIAN
> +		dst[2] = r;
> +		dst[1] = g;
> +		dst[0] = b;
> +#elif defined(__BIG_ENDIAN)
> +		dst[0] = r;
> +		dst[1] = g;
> +		dst[2] = b;
> +#endif
> +		break;
> +	case DRM_FORMAT_XRGB8888:
> +	case DRM_FORMAT_ARGB8888:
> +		r >>= 8;
> +		g >>= 8;
> +		b >>= 8;
> +		put_unaligned((u32)((r << 16) | (g << 8) | b), (u32 *)dst);
> +		break;
> +	case DRM_FORMAT_ABGR8888:
> +		r >>= 8;
> +		g >>= 8;
> +		b >>= 8;
> +		put_unaligned((u32)((b << 16) | (g << 8) | r), (u32 *)dst);
> +		break;
> +	case DRM_FORMAT_XRGB2101010:
> +	case DRM_FORMAT_ARGB2101010:
> +		r >>= 4;
> +		g >>= 4;
> +		b >>= 4;
> +		put_unaligned((u32)((r << 20) | (g << 10) | b), (u32 *)dst);
> +		break;
> +	}
> +}
> +
> +static void sdrm_blit_from_xrgb8888(const u8 *src, u32 src_stride, u32 src_bpp,
> +				    u8 *dst, u32 dst_stride, u32 dst_bpp,
> +				    u32 dst_four_cc, u32 width, u32 height)
> +{
> +	u32 val, i;
> +
> +	while (height--) {
> +		for (i = 0; i < width; ++i) {
> +			val = get_unaligned((const u32 *)&src[i * src_bpp]);
> +			sdrm_put(&dst[i * dst_bpp], dst_four_cc,
> +				 (val & 0x00ff0000U) >> 8,
> +				 (val & 0x0000ff00U),
> +				 (val & 0x000000ffU) << 8);
> +		}
> +
> +		src += src_stride;
> +		dst += dst_stride;
> +	}
> +}
> +
> +static void sdrm_blit_from_rgb565(const u8 *src, u32 src_stride, u32 src_bpp,
> +				  u8 *dst, u32 dst_stride, u32 dst_bpp,
> +				  u32 dst_four_cc, u32 width, u32 height)
> +{
> +	u32 val, i;
> +
> +	while (height--) {
> +		for (i = 0; i < width; ++i) {
> +			val = get_unaligned((const u16 *)&src[i * src_bpp]);
> +			sdrm_put(&dst[i * dst_bpp], dst_four_cc,
> +				 (val & 0xf800),
> +				 (val & 0x07e0) << 5,
> +				 (val & 0x001f) << 11);
> +		}
> +
> +		src += src_stride;
> +		dst += dst_stride;
> +	}
> +}
> +
> +static void sdrm_blit_lines(const u8 *src, u32 src_stride,
> +			    u8 *dst, u32 dst_stride,
> +			    u32 bpp, u32 width, u32 height)
> +{
> +	u32 len;
> +
> +	len = width * bpp;
> +
> +	while (height--) {
> +		memcpy(dst, src, len);
> +		src += src_stride;
> +		dst += dst_stride;
> +	}
> +}
> +
> +static void sdrm_blit(struct sdrm_framebuffer *sfb, u32 x, u32 y,
> +		      u32 width, u32 height)
> +{
> +	struct drm_framebuffer *fb = &sfb->base;
> +	struct drm_device *ddev = fb->dev;
> +	struct sdrm_device *sdrm = ddev->dev_private;
> +	u32 src_bpp, dst_bpp;
> +	u8 *src, *dst;
> +
> +	/* already unmapped; ongoing handover? */
> +	if (!sdrm->fb_map)
> +		return;
> +
> +	/* get buffer offsets */
> +	src = sfb->obj->vmapping;
> +	dst = sdrm->fb_map;
> +
> +	/* bo is guaranteed to be big enough; size checks not needed */
> +	src_bpp = (fb->bits_per_pixel + 7) / 8;
> +	src += fb->offsets[0] + y * fb->pitches[0] + x * src_bpp;
> +
> +	dst_bpp = (sdrm->fb_bpp + 7) / 8;
> +	dst += y * sdrm->fb_stride + x * dst_bpp;
> +
> +	/* if formats are identical, do a line-by-line copy.. */
> +	if (fb->pixel_format == sdrm->fb_format) {
> +		sdrm_blit_lines(src, fb->pitches[0],
> +				dst, sdrm->fb_stride,
> +				src_bpp, width, height);
> +		return;
> +	}
> +
> +	/* ..otherwise call slow blit-function */
> +	switch (fb->pixel_format) {
> +	case DRM_FORMAT_ARGB8888:
> +		/* fallthrough */
> +	case DRM_FORMAT_XRGB8888:
> +		sdrm_blit_from_xrgb8888(src, fb->pitches[0], src_bpp,
> +					dst, sdrm->fb_stride, dst_bpp,
> +					sdrm->fb_format, width, height);
> +		break;
> +	case DRM_FORMAT_RGB565:
> +		sdrm_blit_from_rgb565(src, fb->pitches[0], src_bpp,
> +				      dst, sdrm->fb_stride, dst_bpp,
> +				      sdrm->fb_format, width, height);
> +		break;
> +	}
> +}
> +
> +int sdrm_dirty(struct drm_framebuffer *fb,
> +	       struct drm_file *file,
> +	       unsigned int flags, unsigned int color,
> +	       struct drm_clip_rect *clips,
> +	       unsigned int num_clips)
> +{
> +	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
> +	struct drm_device *ddev = fb->dev;
> +	struct sdrm_device *sdrm = ddev->dev_private;
> +	struct drm_clip_rect full_clip;
> +	unsigned int i;
> +	int r;
> +
> +	if (!clips || !num_clips) {
> +		full_clip.x1 = 0;
> +		full_clip.x2 = fb->width;
> +		full_clip.y1 = 0;
> +		full_clip.y2 = fb->height;
> +		clips = &full_clip;
> +		num_clips = 1;
> +	}
> +
> +	drm_modeset_lock_all(ddev);
> +
> +	if (sdrm->pipe.plane.fb != fb) {
> +		r = 0;
> +		goto unlock;
> +	}
> +
> +	for (i = 0; i < num_clips; i++) {
> +		if (clips[i].x1 > clips[i].x2 || clips[i].x2 > fb->width ||
> +		    clips[i].y1 > clips[i].y2 || clips[i].y2 > fb->height)
> +			continue;
> +
> +		sdrm_blit(sfb, clips[i].x1, clips[i].y1,
> +			  clips[i].x2 - clips[i].x1,
> +			  clips[i].y2 - clips[i].y1);
> +	}
> +
> +unlock:
> +	drm_modeset_unlock_all(ddev);
> +	return 0;
> +}
> +
> +int sdrm_dirty_all_locked(struct sdrm_device *sdrm)
> +{
> +	struct drm_framebuffer *fb;
> +	struct sdrm_framebuffer *sfb;
> +
> +	fb = sdrm->pipe.plane.fb;
> +	if (!fb)
> +		return 0;
> +
> +	sfb = to_sdrm_fb(fb);
> +
> +	sdrm_blit(sfb, 0, 0, fb->width, fb->height);
> +
> +	return 0;
> +}
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> new file mode 100644
> index 0000000..17c1b55
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> @@ -0,0 +1,543 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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 <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/errno.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/simplefb.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/string.h>
> +
> +#include "simpledrm.h"
> +
> +static const struct file_operations sdrm_drm_fops = {
> +	.owner = THIS_MODULE,
> +	.open = drm_open,
> +	.mmap = sdrm_drm_mmap,
> +	.poll = drm_poll,
> +	.read = drm_read,
> +	.unlocked_ioctl = drm_ioctl,
> +	.release = drm_release,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl = drm_compat_ioctl,
> +#endif
> +	.llseek = noop_llseek,
> +};
> +
> +static const struct vm_operations_struct sdrm_gem_vm_ops = {
> +	.fault = sdrm_gem_fault,
> +	.open = drm_gem_vm_open,
> +	.close = drm_gem_vm_close,
> +};
> +
> +static struct drm_driver sdrm_drm_driver = {
> +	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
> +	.fops = &sdrm_drm_fops,
> +
> +	.gem_free_object = sdrm_gem_free_object,
> +	.gem_vm_ops = &sdrm_gem_vm_ops,
> +
> +	.dumb_create = sdrm_dumb_create,
> +	.dumb_map_offset = sdrm_dumb_map_offset,
> +	.dumb_destroy = drm_gem_dumb_destroy,
> +
> +	.name = "simpledrm",
> +	.desc = "Simple firmware framebuffer DRM driver",
> +	.date = "20130601",
> +	.major = 0,
> +	.minor = 0,
> +	.patchlevel = 1,
> +};
> +
> +#if defined CONFIG_OF && defined CONFIG_COMMON_CLK
> +/*
> + * Clock handling code.
> + *
> + * Here we handle the clocks property of our "simple-framebuffer" dt node.
> + * This is necessary so that we can make sure that any clocks needed by
> + * the display engine that the bootloader set up for us (and for which it
> + * provided a simplefb dt node), stay up, for the life of the simplefb
> + * driver.
> + *
> + * When the driver unloads, we cleanly disable, and then release the clocks.
> + *
> + * We only complain about errors here, no action is taken as the most likely
> + * error can only happen due to a mismatch between the bootloader which set
> + * up simplefb, and the clock definitions in the device tree. Chances are
> + * that there are no adverse effects, and if there are, a clean teardown of
> + * the fb probe will not help us much either. So just complain and carry on,
> + * and hope that the user actually gets a working fb at the end of things.
> + */
> +static int sdrm_clocks_init(struct sdrm_device *sdrm,
> +			    struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct clk *clock;
> +	int i, ret;
> +
> +	if (dev_get_platdata(&pdev->dev) || !np)
> +		return 0;
> +
> +	sdrm->clk_count = of_clk_get_parent_count(np);
> +	if (!sdrm->clk_count)
> +		return 0;
> +
> +	sdrm->clks = kcalloc(sdrm->clk_count, sizeof(struct clk *), GFP_KERNEL);
> +	if (!sdrm->clks)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < sdrm->clk_count; i++) {
> +		clock = of_clk_get(np, i);
> +		if (IS_ERR(clock)) {
> +			if (PTR_ERR(clock) == -EPROBE_DEFER) {
> +				while (--i >= 0) {
> +					if (sdrm->clks[i])
> +						clk_put(sdrm->clks[i]);
> +				}
> +				kfree(sdrm->clks);
> +				return -EPROBE_DEFER;
> +			}
> +			dev_err(&pdev->dev, "%s: clock %d not found: %ld\n",
> +				__func__, i, PTR_ERR(clock));
> +			continue;
> +		}
> +		sdrm->clks[i] = clock;
> +	}
> +
> +	for (i = 0; i < sdrm->clk_count; i++) {
> +		if (sdrm->clks[i]) {
> +			ret = clk_prepare_enable(sdrm->clks[i]);
> +			if (ret) {
> +				dev_err(&pdev->dev,
> +					"%s: failed to enable clock %d: %d\n",
> +					__func__, i, ret);
> +				clk_put(sdrm->clks[i]);
> +				sdrm->clks[i] = NULL;
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void sdrm_clocks_destroy(struct sdrm_device *sdrm)
> +{
> +	int i;
> +
> +	if (!sdrm->clks)
> +		return;
> +
> +	for (i = 0; i < sdrm->clk_count; i++) {
> +		if (sdrm->clks[i]) {
> +			clk_disable_unprepare(sdrm->clks[i]);
> +			clk_put(sdrm->clks[i]);
> +		}
> +	}
> +
> +	kfree(sdrm->clks);
> +}
> +#else
> +static int sdrm_clocks_init(struct sdrm_device *sdrm,
> +			    struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static void sdrm_clocks_destroy(struct sdrm_device *sdrm)
> +{
> +}
> +#endif
> +
> +#if defined CONFIG_OF && defined CONFIG_REGULATOR
> +
> +#define SUPPLY_SUFFIX "-supply"
> +
> +/*
> + * Regulator handling code.
> + *
> + * Here we handle the num-supplies and vin*-supply properties of our
> + * "simple-framebuffer" dt node. This is necessary so that we can make sure
> + * that any regulators needed by the display hardware that the bootloader
> + * set up for us (and for which it provided a simplefb dt node), stay up,
> + * for the life of the simplefb driver.
> + *
> + * When the driver unloads, we cleanly disable, and then release the
> + * regulators.
> + *
> + * We only complain about errors here, no action is taken as the most likely
> + * error can only happen due to a mismatch between the bootloader which set
> + * up simplefb, and the regulator definitions in the device tree. Chances are
> + * that there are no adverse effects, and if there are, a clean teardown of
> + * the fb probe will not help us much either. So just complain and carry on,
> + * and hope that the user actually gets a working fb at the end of things.
> + */
> +static int sdrm_regulators_init(struct sdrm_device *sdrm,
> +				struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct regulator *regulator;
> +	int count = 0, i = 0, ret;
> +	struct property *prop;
> +	const char *p;
> +
> +	if (dev_get_platdata(&pdev->dev) || !np)
> +		return 0;
> +
> +	/* Count the number of regulator supplies */
> +	for_each_property_of_node(np, prop) {
> +		p = strstr(prop->name, SUPPLY_SUFFIX);
> +		if (p && p != prop->name)
> +			count++;
> +	}
> +
> +	if (!count)
> +		return 0;
> +
> +	sdrm->regulators = devm_kcalloc(&pdev->dev, count,
> +					sizeof(struct regulator *),
> +					GFP_KERNEL);
> +	if (!sdrm->regulators)
> +		return -ENOMEM;
> +
> +	/* Get all the regulators */
> +	for_each_property_of_node(np, prop) {
> +		char name[32]; /* 32 is max size of property name */
> +
> +		p = strstr(prop->name, SUPPLY_SUFFIX);
> +		if (!p || p == prop->name)
> +			continue;
> +
> +		strlcpy(name, prop->name,
> +			strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1);
> +		regulator = devm_regulator_get_optional(&pdev->dev, name);
> +		if (IS_ERR(regulator)) {
> +			if (PTR_ERR(regulator) == -EPROBE_DEFER)
> +				return -EPROBE_DEFER;
> +			dev_err(&pdev->dev, "regulator %s not found: %ld\n",
> +				name, PTR_ERR(regulator));
> +			continue;
> +		}
> +		sdrm->regulators[i++] = regulator;
> +	}
> +	sdrm->regulator_count = i;
> +
> +	/* Enable all the regulators */
> +	for (i = 0; i < sdrm->regulator_count; i++) {
> +		ret = regulator_enable(sdrm->regulators[i]);
> +		if (ret) {
> +			dev_err(&pdev->dev,
> +				"failed to enable regulator %d: %d\n",
> +				i, ret);
> +			devm_regulator_put(sdrm->regulators[i]);
> +			sdrm->regulators[i] = NULL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void sdrm_regulators_destroy(struct sdrm_device *sdrm)
> +{
> +	int i;
> +
> +	if (!sdrm->regulators)
> +		return;
> +
> +	for (i = 0; i < sdrm->regulator_count; i++)
> +		if (sdrm->regulators[i])
> +			regulator_disable(sdrm->regulators[i]);
> +}
> +#else
> +static int sdrm_regulators_init(struct sdrm_device *sdrm,
> +				struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static void sdrm_regulators_destroy(struct sdrm_device *sdrm)
> +{
> +}
> +#endif
> +
> +static int parse_dt(struct platform_device *pdev,
> +		    struct simplefb_platform_data *mode)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	const char *format;
> +	int ret;
> +
> +	if (!np)
> +		return -ENODEV;
> +
> +	ret = of_property_read_u32(np, "width", &mode->width);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Can't parse width property\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(np, "height", &mode->height);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Can't parse height property\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(np, "stride", &mode->stride);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Can't parse stride property\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_string(np, "format", &format);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Can't parse format property\n");
> +		return ret;
> +	}
> +	mode->format = format;
> +
> +	return 0;
> +}
> +
> +static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS;
> +
> +static int sdrm_pdev_init(struct sdrm_device *sdrm,
> +			  struct platform_device *pdev)
> +{
> +	struct simplefb_platform_data *mode = pdev->dev.platform_data;
> +	struct simplefb_platform_data pmode;
> +	struct resource *mem;
> +	unsigned int depth;
> +	int ret, i, bpp;
> +
> +	if (!mode) {
> +		mode = &pmode;
> +		ret = parse_dt(pdev, mode);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!mem) {
> +		dev_err(sdrm->ddev->dev, "No memory resource\n");
> +		return -ENODEV;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(simplefb_formats); ++i) {
> +		if (strcmp(mode->format, simplefb_formats[i].name))
> +			continue;
> +
> +		sdrm->fb_sformat = &simplefb_formats[i];
> +		sdrm->fb_format = simplefb_formats[i].fourcc;
> +		sdrm->fb_width = mode->width;
> +		sdrm->fb_height = mode->height;
> +		sdrm->fb_stride = mode->stride;
> +		sdrm->fb_base = mem->start;
> +		sdrm->fb_size = resource_size(mem);
> +		break;
> +	}
> +
> +	if (i >= ARRAY_SIZE(simplefb_formats)) {
> +		dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
> +		return -ENODEV;
> +	}
> +
> +	switch (sdrm->fb_format) {
> +	case DRM_FORMAT_RGB565:
> +	case DRM_FORMAT_XRGB1555:
> +	case DRM_FORMAT_ARGB1555:
> +	case DRM_FORMAT_RGB888:
> +	case DRM_FORMAT_XRGB8888:
> +	case DRM_FORMAT_ARGB8888:
> +	case DRM_FORMAT_ABGR8888:
> +	case DRM_FORMAT_XRGB2101010:
> +	case DRM_FORMAT_ARGB2101010:
> +		/*
> +		 * You must adjust sdrm_put() whenever you add a new format
> +		 * here, otherwise, blitting operations will not work.
> +		 * Furthermore, include/linux/platform_data/simplefb.h needs
> +		 * to be adjusted so the platform-device actually allows this
> +		 * format.
> +		 */
> +		break;
> +	default:
> +		dev_err(sdrm->ddev->dev, "Unsupported format %s\n",
> +			mode->format);
> +		return -ENODEV;
> +	}
> +
> +	drm_fb_get_bpp_depth(sdrm->fb_format, &depth, &bpp);
> +	if (!bpp) {
> +		dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
> +		return -ENODEV;
> +	}
> +
> +	if (sdrm->fb_size < sdrm->fb_stride * sdrm->fb_height) {
> +		dev_err(sdrm->ddev->dev, "FB too small\n");
> +		return -ENODEV;
> +	} else if ((bpp + 7) / 8 * sdrm->fb_width > sdrm->fb_stride) {
> +		dev_err(sdrm->ddev->dev, "Invalid stride\n");
> +		return -ENODEV;
> +	}
> +
> +	sdrm->fb_bpp = bpp;
> +
> +	sdrm->fb_map = ioremap_wc(sdrm->fb_base, sdrm->fb_size);
> +	if (!sdrm->fb_map) {
> +		dev_err(sdrm->ddev->dev, "cannot remap VMEM\n");
> +		return -EIO;
> +	}
> +
> +	DRM_DEBUG_KMS("format: %s\n", drm_get_format_name(sdrm->fb_format));
> +
> +	return 0;
> +}
> +
> +static void sdrm_pdev_destroy(struct sdrm_device *sdrm)
> +{
> +	if (sdrm->fb_map) {
> +		iounmap(sdrm->fb_map);
> +		sdrm->fb_map = NULL;
> +	}
> +}
> +
> +static int sdrm_simplefb_probe(struct platform_device *pdev)
> +{
> +	struct sdrm_device *sdrm;
> +	struct drm_device *ddev;
> +	int ret;
> +
> +	ddev = drm_dev_alloc(&sdrm_drm_driver, &pdev->dev);
> +	if (!ddev)
> +		return -ENOMEM;
> +
> +	sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
> +	if (!sdrm)
> +		goto err_free;
> +
> +	ddev->dev_private = sdrm;
> +	sdrm->ddev = ddev;
> +
> +	ret = sdrm_pdev_init(sdrm, pdev);
> +	if (ret)
> +		goto err_free;
> +
> +	ret = sdrm_drm_modeset_init(sdrm);
> +	if (ret)
> +		goto err_destroy;
> +
> +	ret = sdrm_clocks_init(sdrm, pdev);
> +	if (ret < 0)
> +		goto err_cleanup;
> +
> +	ret = sdrm_regulators_init(sdrm, pdev);
> +	if (ret < 0)
> +		goto err_clocks;
> +
> +	platform_set_drvdata(pdev, ddev);
> +	ret = drm_dev_register(ddev, 0);
> +	if (ret)
> +		goto err_regulators;
> +
> +	DRM_INFO("Initialized %s on minor %d\n", ddev->driver->name,
> +		 ddev->primary->index);
> +
> +	return 0;
> +
> +err_regulators:
> +	sdrm_regulators_destroy(sdrm);
> +err_clocks:
> +	sdrm_clocks_destroy(sdrm);
> +err_cleanup:
> +	drm_mode_config_cleanup(ddev);
> +err_destroy:
> +	sdrm_pdev_destroy(sdrm);
> +err_free:
> +	drm_dev_unref(ddev);
> +	kfree(sdrm);
> +
> +	return ret;
> +}
> +
> +static int sdrm_simplefb_remove(struct platform_device *pdev)
> +{
> +	struct drm_device *ddev = platform_get_drvdata(pdev);
> +	struct sdrm_device *sdrm = ddev->dev_private;
> +
> +	drm_dev_unregister(ddev);
> +	drm_mode_config_cleanup(ddev);
> +
> +	/* protect fb_map removal against sdrm_blit() */
> +	drm_modeset_lock_all(ddev);
> +	sdrm_pdev_destroy(sdrm);
> +	drm_modeset_unlock_all(ddev);
> +
> +	sdrm_regulators_destroy(sdrm);
> +	sdrm_clocks_destroy(sdrm);
> +
> +	drm_dev_unref(ddev);
> +	kfree(sdrm);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id simplefb_of_match[] = {
> +	{ .compatible = "simple-framebuffer", },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, simplefb_of_match);
> +
> +static struct platform_driver sdrm_simplefb_driver = {
> +	.probe = sdrm_simplefb_probe,
> +	.remove = sdrm_simplefb_remove,
> +	.driver = {
> +		.name = "simple-framebuffer",
> +		.mod_name = KBUILD_MODNAME,
> +		.owner = THIS_MODULE,
> +		.of_match_table = simplefb_of_match,
> +	},
> +};
> +
> +static int __init sdrm_init(void)
> +{
> +	int ret;
> +
> +	ret = platform_driver_register(&sdrm_simplefb_driver);
> +	if (ret)
> +		return ret;
> +
> +	if (IS_ENABLED(CONFIG_OF_ADDRESS) && of_chosen) {
> +		struct device_node *np;
> +
> +		for_each_child_of_node(of_chosen, np) {
> +			if (of_device_is_compatible(np, "simple-framebuffer"))
> +				of_platform_device_create(np, NULL, NULL);
> +		}
> +	}
> +
> +	return 0;
> +}
> +module_init(sdrm_init);
> +
> +static void __exit sdrm_exit(void)
> +{
> +	platform_driver_unregister(&sdrm_simplefb_driver);
> +}
> +module_exit(sdrm_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
> +MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
> +MODULE_ALIAS("platform:simple-framebuffer");
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
> new file mode 100644
> index 0000000..8cced80
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
> @@ -0,0 +1,202 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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 <linux/errno.h>
> +#include <linux/mm.h>
> +#include <linux/slab.h>
> +#include <linux/vmalloc.h>
> +
> +#include "simpledrm.h"
> +
> +struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
> +					      size_t size)
> +{
> +	struct sdrm_gem_object *obj;
> +
> +	WARN_ON(!size || (size & ~PAGE_MASK) != 0);
> +
> +	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> +	if (!obj)
> +		return NULL;
> +
> +	if (drm_gem_object_init(ddev, &obj->base, size)) {
> +		kfree(obj);
> +		return NULL;
> +	}
> +
> +	return obj;
> +}
> +
> +int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
> +		     struct drm_mode_create_dumb *args)
> +{
> +	struct sdrm_gem_object *obj;
> +	int r;
> +
> +	/* overflow checks are done by DRM core */
> +	args->pitch = (args->bpp + 7) / 8 * args->width;
> +	args->size = PAGE_ALIGN(args->pitch * args->height);
> +
> +	obj = sdrm_gem_alloc_object(ddev, args->size);
> +	if (!obj)
> +		return -ENOMEM;
> +
> +	r = drm_gem_handle_create(dfile, &obj->base, &args->handle);
> +	if (r) {
> +		drm_gem_object_unreference_unlocked(&obj->base);
> +		return r;
> +	}
> +
> +	/* handle owns a reference */
> +	drm_gem_object_unreference_unlocked(&obj->base);
> +
> +	return 0;
> +}
> +
> +int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> +	int ret;
> +
> +	ret = drm_gem_mmap(filp, vma);
> +	if (ret)
> +		return ret;
> +
> +	vma->vm_flags &= ~VM_PFNMAP;
> +	vma->vm_flags |= VM_MIXEDMAP;
> +	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);

Hm, this is still the hand-rolled mmap support. Did my more detailed plan
not work, now that you've switched to more native gem objects? Doing it
that way would allow us to remove all the hand-rolled fault handling
(especially sdrm_gem_fault), which is think would be nice.

I know, udl doesn't do it that way, not sure exactly why.

> +
> +	return 0;
> +}
> +
> +int sdrm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
> +{
> +	struct drm_gem_object *gobj = vma->vm_private_data;
> +	struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
> +	pgoff_t offset;
> +	int ret;
> +
> +	if (!obj->pages)
> +		return VM_FAULT_SIGBUS;
> +
> +	offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >>
> +		 PAGE_SHIFT;
> +
> +	ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address,
> +			     obj->pages[offset]);
> +	switch (ret) {
> +	case -EAGAIN:
> +	case 0:
> +	case -ERESTARTSYS:
> +	case -EINTR:
> +	case -EBUSY:
> +		return VM_FAULT_NOPAGE;
> +
> +	case -ENOMEM:
> +		return VM_FAULT_OOM;
> +	}
> +
> +	return VM_FAULT_SIGBUS;
> +}
> +
> +int sdrm_gem_get_pages(struct sdrm_gem_object *obj)
> +{
> +	struct page **pages;
> +
> +	if (obj->pages)
> +		return 0;
> +
> +	pages = drm_gem_get_pages(&obj->base);
> +	if (IS_ERR(pages))
> +		return PTR_ERR(pages);
> +
> +	obj->pages = pages;
> +
> +	return 0;
> +}
> +
> +static void sdrm_gem_put_pages(struct sdrm_gem_object *obj)
> +{
> +	if (!obj->pages)
> +		return;
> +
> +	drm_gem_put_pages(&obj->base, obj->pages, false, false);
> +	obj->pages = NULL;
> +}
> +
> +int sdrm_gem_vmap(struct sdrm_gem_object *obj)
> +{
> +	int page_count = obj->base.size / PAGE_SIZE;
> +	int ret;
> +
> +	if (obj->vmapping)
> +		return 0;
> +
> +	ret = sdrm_gem_get_pages(obj);
> +	if (ret)
> +		return ret;
> +
> +	obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
> +	if (!obj->vmapping)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
> +static void sdrm_gem_vunmap(struct sdrm_gem_object *obj)
> +{
> +	vunmap(obj->vmapping);
> +	obj->vmapping = NULL;
> +
> +	sdrm_gem_put_pages(obj);
> +}
> +
> +void sdrm_gem_free_object(struct drm_gem_object *gobj)
> +{
> +	struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
> +
> +	if (obj->vmapping)
> +		sdrm_gem_vunmap(obj);
> +
> +	if (obj->pages)
> +		sdrm_gem_put_pages(obj);
> +
> +	drm_gem_object_release(gobj);
> +	kfree(obj);
> +}
> +
> +int sdrm_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
> +			 uint32_t handle, uint64_t *offset)
> +{
> +	struct sdrm_gem_object *obj;
> +	struct drm_gem_object *gobj;
> +	int ret;
> +
> +	gobj = drm_gem_object_lookup(dfile, handle);
> +	if (!gobj)
> +		return -ENOENT;
> +
> +	obj = to_sdrm_bo(gobj);
> +
> +	ret = sdrm_gem_get_pages(obj);
> +	if (ret)
> +		goto out_unref;
> +
> +	ret = drm_gem_create_mmap_offset(gobj);
> +	if (ret)
> +		goto out_unref;
> +
> +	*offset = drm_vma_node_offset_addr(&gobj->vma_node);
> +
> +out_unref:
> +	drm_gem_object_unreference_unlocked(gobj);
> +
> +	return ret;
> +}
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
> new file mode 100644
> index 0000000..e6dc3df
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
> @@ -0,0 +1,234 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_gem.h>
> +#include <drm/drm_simple_kms_helper.h>
> +#include <linux/slab.h>
> +
> +#include "simpledrm.h"
> +
> +static const uint32_t sdrm_formats[] = {
> +	DRM_FORMAT_RGB565,
> +	DRM_FORMAT_ARGB8888,
> +	DRM_FORMAT_XRGB8888,
> +};
> +
> +static int sdrm_conn_get_modes(struct drm_connector *conn)
> +{
> +	struct sdrm_device *sdrm = conn->dev->dev_private;
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_cvt_mode(sdrm->ddev, sdrm->fb_width, sdrm->fb_height,
> +			    60, false, false, false);
> +	if (!mode)
> +		return 0;
> +
> +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> +	drm_mode_set_name(mode);
> +	drm_mode_probed_add(conn, mode);
> +
> +	return 1;
> +}
> +
> +static const struct drm_connector_helper_funcs sdrm_conn_hfuncs = {
> +	.get_modes = sdrm_conn_get_modes,
> +	.best_encoder = drm_atomic_helper_best_encoder,
> +};
> +
> +static enum drm_connector_status sdrm_conn_detect(struct drm_connector *conn,
> +						  bool force)
> +{
> +	/*
> +	 * We simulate an always connected monitor. simple-fb doesn't
> +	 * provide any way to detect whether the connector is active. Hence,
> +	 * signal DRM core that it is always connected.
> +	 */
> +
> +	return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs sdrm_conn_ops = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.detect = sdrm_conn_detect,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = drm_connector_cleanup,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static inline struct sdrm_device *
> +pipe_to_sdrm(struct drm_simple_display_pipe *pipe)
> +{
> +	return container_of(pipe, struct sdrm_device, pipe);
> +}
> +
> +static void sdrm_crtc_send_vblank_event(struct drm_crtc *crtc)
> +{
> +	if (crtc->state && crtc->state->event) {
> +		spin_lock_irq(&crtc->dev->event_lock);
> +		drm_crtc_send_vblank_event(crtc, crtc->state->event);
> +		spin_unlock_irq(&crtc->dev->event_lock);
> +		crtc->state->event = NULL;
> +	}
> +}
> +
> +void sdrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
> +			      struct drm_plane_state *plane_state)
> +{
> +	struct drm_framebuffer *fb = pipe->plane.state->fb;
> +	struct sdrm_device *sdrm = pipe_to_sdrm(pipe);
> +
> +	sdrm_crtc_send_vblank_event(&pipe->crtc);
> +
> +	if (fb) {
> +		pipe->plane.fb = fb;
> +		sdrm_dirty_all_locked(sdrm);
> +	}
> +}
> +
> +static void sdrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
> +				     struct drm_crtc_state *crtc_state)
> +{
> +	sdrm_crtc_send_vblank_event(&pipe->crtc);
> +}
> +
> +static void sdrm_display_pipe_disable(struct drm_simple_display_pipe *pipe)
> +{
> +	sdrm_crtc_send_vblank_event(&pipe->crtc);

The above two send_vblank_event calls shouldn't be needed, and strictly
speaking they're buggy because they can send out the event too early. I
just helped debug someone this exact failure, and I think I have an
(unconfirmed) bugfix for the simple helpers. Will send it out now for you
to test. It should be enough to just send out events from the update hook.

Besides this and the mmap issue above I think this now all looks really
good. A bit much boilerplate in the gem code, but that's expected since we
simply don't have many gem shmem based drivers yet where extracting more
helpers would make sense.

Thanks, Daniel

> +}
> +
> +static const struct drm_simple_display_pipe_funcs sdrm_pipe_funcs = {
> +	.update = sdrm_display_pipe_update,
> +	.enable = sdrm_display_pipe_enable,
> +	.disable = sdrm_display_pipe_disable,
> +};
> +
> +static int sdrm_fb_create_handle(struct drm_framebuffer *fb,
> +				 struct drm_file *dfile,
> +				 unsigned int *handle)
> +{
> +	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
> +
> +	return drm_gem_handle_create(dfile, &sfb->obj->base, handle);
> +}
> +
> +static void sdrm_fb_destroy(struct drm_framebuffer *fb)
> +{
> +	struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
> +
> +	drm_framebuffer_cleanup(fb);
> +	drm_gem_object_unreference_unlocked(&sfb->obj->base);
> +	kfree(sfb);
> +}
> +
> +static const struct drm_framebuffer_funcs sdrm_fb_ops = {
> +	.create_handle = sdrm_fb_create_handle,
> +	.dirty = sdrm_dirty,
> +	.destroy = sdrm_fb_destroy,
> +};
> +
> +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)
> +{
> +	fb->obj = obj;
> +	drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd);
> +
> +	return drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops);
> +}
> +
> +static struct drm_framebuffer *sdrm_fb_create(struct drm_device *ddev,
> +					      struct drm_file *dfile,
> +					      const struct drm_mode_fb_cmd2 *cmd)
> +{
> +	struct sdrm_framebuffer *fb;
> +	struct drm_gem_object *gobj;
> +	int ret;
> +
> +	if (cmd->flags)
> +		return ERR_PTR(-EINVAL);
> +
> +	gobj = drm_gem_object_lookup(dfile, cmd->handles[0]);
> +	if (!gobj)
> +		return ERR_PTR(-EINVAL);
> +
> +	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
> +	if (!fb) {
> +		ret = -ENOMEM;
> +		goto err_unref;
> +	}
> +
> +	ret = sdrm_fb_init(ddev, fb, cmd, to_sdrm_bo(gobj));
> +	if (ret)
> +		goto err_free;
> +
> +	ret = sdrm_gem_vmap(fb->obj);
> +	if (ret)
> +		goto err_free;
> +
> +	DRM_DEBUG_KMS("[FB:%d] pixel_format: %s\n", fb->base.base.id,
> +		      drm_get_format_name(fb->base.pixel_format));
> +
> +	return &fb->base;
> +
> +err_free:
> +	kfree(fb);
> +err_unref:
> +	drm_gem_object_unreference_unlocked(gobj);
> +
> +	return ERR_PTR(ret);
> +}
> +
> +static const struct drm_mode_config_funcs sdrm_mode_config_ops = {
> +	.fb_create = sdrm_fb_create,
> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +int sdrm_drm_modeset_init(struct sdrm_device *sdrm)
> +{
> +	struct drm_connector *conn = &sdrm->conn;
> +	struct drm_device *ddev = sdrm->ddev;
> +	int ret;
> +
> +	drm_mode_config_init(ddev);
> +	ddev->mode_config.min_width = sdrm->fb_width;
> +	ddev->mode_config.max_width = sdrm->fb_width;
> +	ddev->mode_config.min_height = sdrm->fb_height;
> +	ddev->mode_config.max_height = sdrm->fb_height;
> +	ddev->mode_config.preferred_depth = sdrm->fb_bpp;
> +	ddev->mode_config.funcs = &sdrm_mode_config_ops;
> +
> +	drm_connector_helper_add(conn, &sdrm_conn_hfuncs);
> +	ret = drm_connector_init(ddev, conn, &sdrm_conn_ops,
> +				 DRM_MODE_CONNECTOR_VIRTUAL);
> +	if (ret)
> +		goto err_cleanup;
> +
> +	ret = drm_simple_display_pipe_init(ddev, &sdrm->pipe, &sdrm_pipe_funcs,
> +					   sdrm_formats,
> +					   ARRAY_SIZE(sdrm_formats), conn);
> +	if (ret)
> +		goto err_cleanup;
> +
> +	drm_mode_config_reset(ddev);
> +
> +	return 0;
> +
> +err_cleanup:
> +	drm_mode_config_cleanup(ddev);
> +
> +	return ret;
> +}
> --
> 2.8.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers()
  2016-08-22 20:25 ` [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers() Noralf Trønnes
@ 2016-08-23 12:41   ` Daniel Vetter
  2016-08-23 17:52     ` Noralf Trønnes
  0 siblings, 1 reply; 19+ messages in thread
From: Daniel Vetter @ 2016-08-23 12:41 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-kernel, devicetree

On Mon, Aug 22, 2016 at 10:25:25PM +0200, Noralf Trønnes wrote:
> There is currently no non-fbdev mechanism in place to kick out
> simpledrm when the real hw-driver is probed. As a stop gap until
> that is in place, honour remove_conflicting_framebuffers() and
> delete the simple-framebuffer platform device when it's called.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
> 
> Changes from version 3:
> - drm_device.platformdev is deprecated, use to_platform_device(ddev->dev).
> - fb_helper might have been released in sdrm_fbdev_fb_destroy(),
>   so open code drm_fb_helper_release_fbi()
> - Strengthen the test in sdrm_fbdev_event_notify() that we're the one.
> 
> Changes from version 2:
> - Don't forget to free fb_info when kicked out.
> 
>  drivers/gpu/drm/simpledrm/Kconfig           |  5 +++
>  drivers/gpu/drm/simpledrm/simpledrm.h       |  2 +
>  drivers/gpu/drm/simpledrm/simpledrm_drv.c   |  3 ++
>  drivers/gpu/drm/simpledrm/simpledrm_fbdev.c | 62 ++++++++++++++++++++++++++++-
>  4 files changed, 70 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
> index 3257590..4275d13 100644
> --- a/drivers/gpu/drm/simpledrm/Kconfig
> +++ b/drivers/gpu/drm/simpledrm/Kconfig
> @@ -16,6 +16,11 @@ config DRM_SIMPLEDRM
>  	  If fbdev support is enabled, this driver will also provide an fbdev
>  	  compatibility layer that supports fbcon, mmap is not supported.
> 
> +	  WARNING
> +	  fbdev must be enabled for simpledrm to disable itself when a real
> +	  hw-driver is probed. It relies on remove_conflicting_framebuffers()
> +	  to be called by the hw-driver.
> +
>  	  If unsure, say Y.
> 
>  	  To compile this driver as a module, choose M here: the
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
> index d4eb52c..3cca196 100644
> --- a/drivers/gpu/drm/simpledrm/simpledrm.h
> +++ b/drivers/gpu/drm/simpledrm/simpledrm.h
> @@ -87,5 +87,7 @@ int sdrm_fb_init(struct drm_device *ddev, struct sdrm_framebuffer *fb,
>  		 struct sdrm_gem_object *obj);
>  void sdrm_fbdev_init(struct sdrm_device *sdrm);
>  void sdrm_fbdev_cleanup(struct sdrm_device *sdrm);
> +void sdrm_fbdev_kickout_init(void);
> +void sdrm_fbdev_kickout_exit(void);
> 
>  #endif /* SDRM_DRV_H */
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> index fe752c6..0750652 100644
> --- a/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> @@ -531,12 +531,15 @@ static int __init sdrm_init(void)
>  		}
>  	}
> 
> +	sdrm_fbdev_kickout_init();
> +
>  	return 0;
>  }
>  module_init(sdrm_init);
> 
>  static void __exit sdrm_exit(void)
>  {
> +	sdrm_fbdev_kickout_exit();
>  	platform_driver_unregister(&sdrm_simplefb_driver);
>  }
>  module_exit(sdrm_exit);
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
> index c6596ad..7c6db2c 100644
> --- a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
> @@ -44,6 +44,20 @@ static int sdrm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
>  	return -ENODEV;
>  }
> 
> +/*
> + * Releasing has to be done outside the notifier callchain when we're
> + * kicked out, since do_unregister_framebuffer() calls put_fb_info()
> + * after the notifier has run.
> + * Open code drm_fb_helper_release_fbi() since fb_helper is freed at
> + * this point when kicked out.
> + */
> +static void sdrm_fbdev_fb_destroy(struct fb_info *info)
> +{
> +	if (info->cmap.len)
> +		fb_dealloc_cmap(&info->cmap);
> +	framebuffer_release(info);
> +}
> +
>  static struct fb_ops sdrm_fbdev_ops = {
>  	.owner		= THIS_MODULE,
>  	.fb_fillrect	= drm_fb_helper_sys_fillrect,
> @@ -53,6 +67,7 @@ static struct fb_ops sdrm_fbdev_ops = {
>  	.fb_set_par	= drm_fb_helper_set_par,
>  	.fb_setcmap	= drm_fb_helper_setcmap,
>  	.fb_mmap	= sdrm_fb_mmap,
> +	.fb_destroy	= sdrm_fbdev_fb_destroy,
>  };
> 
>  static int sdrm_fbdev_create(struct drm_fb_helper *helper,
> @@ -110,6 +125,9 @@ static int sdrm_fbdev_create(struct drm_fb_helper *helper,
>  	fbi->screen_base = obj->vmapping;
>  	fbi->fix.smem_len = sdrm->fb_size;
> 
> +	fbi->apertures->ranges[0].base = sdrm->fb_base;
> +	fbi->apertures->ranges[0].size = sdrm->fb_size;
> +
>  	return 0;
> 
>  err_fbi_release:
> @@ -188,9 +206,13 @@ void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
>  	sdrm->fb_helper = NULL;
>  	fbdev = to_sdrm_fbdev(fb_helper);
> 
> -	drm_fb_helper_unregister_fbi(fb_helper);
> +	/* it might have been kicked out */
> +	if (registered_fb[fbdev->fb_helper.fbdev->node])
> +		drm_fb_helper_unregister_fbi(fb_helper);
> +
> +	/* freeing fb_info is done in fb_ops.fb_destroy() */
> +
>  	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);
> @@ -199,3 +221,39 @@ void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
>  	drm_fb_helper_fini(fb_helper);
>  	kfree(fbdev);
>  }
> +
> +static int sdrm_fbdev_event_notify(struct notifier_block *self,
> +				   unsigned long action, void *data)
> +{
> +	struct sdrm_device *sdrm;
> +	struct fb_event *event = data;
> +	struct fb_info *info = event->info;
> +	struct drm_fb_helper *fb_helper = info->par;
> +
> +	if (action != FB_EVENT_FB_UNREGISTERED)
> +		return NOTIFY_DONE;
> +
> +	if (!fb_helper || !fb_helper->dev || fb_helper->fbdev != info)
> +		return NOTIFY_DONE;
> +
> +	sdrm = fb_helper->dev->dev_private;
> +
> +	if (sdrm && sdrm->fb_helper == fb_helper)
> +		platform_device_del(to_platform_device(fb_helper->dev->dev));
> +
> +	return NOTIFY_DONE;
> +}

One problem this leaves behind is that registering of the new fbdev driver
is too late - by that point we've already set up the entire driver,
including modeset. If fbdev meanwhile does a dpms off or something like
that all hell will break loose.

I think the only option is to add a new notifier chain for fbdev removal,
called from remove_conflicting_framebuffers (even for CONFIG_FB=n, so need
a fallback in core/fb_notify.c like with the other notifier I think). That
would at least keep things working if fbdev is entirely disabled. Or have
I entirely misunderstood how this works?

Oh and another one, which we learned the hard way with i915: We probably
need to get rid of vgacon too. And again we need to do that _before_ we
start touching the hardware. I think simpledrm probably needs that too.
But since simpledrm doesn't know on which pci device it sits it probably
won't know if the same device is decoding vga signals, so no one knows how
that is supposed to work :( One thing at the time probably.
-Daniel

> +
> +static struct notifier_block sdrm_fbdev_event_notifier = {
> +	.notifier_call  = sdrm_fbdev_event_notify,
> +};
> +
> +void sdrm_fbdev_kickout_init(void)
> +{
> +	fb_register_client(&sdrm_fbdev_event_notifier);
> +}
> +
> +void sdrm_fbdev_kickout_exit(void)
> +{
> +	fb_unregister_client(&sdrm_fbdev_event_notifier);
> +}
> --
> 2.8.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers()
  2016-08-23 12:41   ` Daniel Vetter
@ 2016-08-23 17:52     ` Noralf Trønnes
  2016-08-23 18:01       ` Daniel Vetter
  0 siblings, 1 reply; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-23 17:52 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, linux-kernel, devicetree


Den 23.08.2016 14:41, skrev Daniel Vetter:
> On Mon, Aug 22, 2016 at 10:25:25PM +0200, Noralf Trønnes wrote:
>> There is currently no non-fbdev mechanism in place to kick out
>> simpledrm when the real hw-driver is probed. As a stop gap until
>> that is in place, honour remove_conflicting_framebuffers() and
>> delete the simple-framebuffer platform device when it's called.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---

<snip>

>> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
>> index c6596ad..7c6db2c 100644
>> --- a/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
>> +++ b/drivers/gpu/drm/simpledrm/simpledrm_fbdev.c
>> @@ -44,6 +44,20 @@ static int sdrm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
>>   	return -ENODEV;
>>   }
>>
>> +/*
>> + * Releasing has to be done outside the notifier callchain when we're
>> + * kicked out, since do_unregister_framebuffer() calls put_fb_info()
>> + * after the notifier has run.
>> + * Open code drm_fb_helper_release_fbi() since fb_helper is freed at
>> + * this point when kicked out.
>> + */
>> +static void sdrm_fbdev_fb_destroy(struct fb_info *info)
>> +{
>> +	if (info->cmap.len)
>> +		fb_dealloc_cmap(&info->cmap);
>> +	framebuffer_release(info);
>> +}
>> +
>>   static struct fb_ops sdrm_fbdev_ops = {
>>   	.owner		= THIS_MODULE,
>>   	.fb_fillrect	= drm_fb_helper_sys_fillrect,
>> @@ -53,6 +67,7 @@ static struct fb_ops sdrm_fbdev_ops = {
>>   	.fb_set_par	= drm_fb_helper_set_par,
>>   	.fb_setcmap	= drm_fb_helper_setcmap,
>>   	.fb_mmap	= sdrm_fb_mmap,
>> +	.fb_destroy	= sdrm_fbdev_fb_destroy,
>>   };
>>
>>   static int sdrm_fbdev_create(struct drm_fb_helper *helper,
>> @@ -110,6 +125,9 @@ static int sdrm_fbdev_create(struct drm_fb_helper *helper,
>>   	fbi->screen_base = obj->vmapping;
>>   	fbi->fix.smem_len = sdrm->fb_size;
>>
>> +	fbi->apertures->ranges[0].base = sdrm->fb_base;
>> +	fbi->apertures->ranges[0].size = sdrm->fb_size;
>> +
>>   	return 0;
>>
>>   err_fbi_release:
>> @@ -188,9 +206,13 @@ void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
>>   	sdrm->fb_helper = NULL;
>>   	fbdev = to_sdrm_fbdev(fb_helper);
>>
>> -	drm_fb_helper_unregister_fbi(fb_helper);
>> +	/* it might have been kicked out */
>> +	if (registered_fb[fbdev->fb_helper.fbdev->node])
>> +		drm_fb_helper_unregister_fbi(fb_helper);
>> +
>> +	/* freeing fb_info is done in fb_ops.fb_destroy() */
>> +
>>   	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);
>> @@ -199,3 +221,39 @@ void sdrm_fbdev_cleanup(struct sdrm_device *sdrm)
>>   	drm_fb_helper_fini(fb_helper);
>>   	kfree(fbdev);
>>   }
>> +
>> +static int sdrm_fbdev_event_notify(struct notifier_block *self,
>> +				   unsigned long action, void *data)
>> +{
>> +	struct sdrm_device *sdrm;
>> +	struct fb_event *event = data;
>> +	struct fb_info *info = event->info;
>> +	struct drm_fb_helper *fb_helper = info->par;
>> +
>> +	if (action != FB_EVENT_FB_UNREGISTERED)
>> +		return NOTIFY_DONE;
>> +
>> +	if (!fb_helper || !fb_helper->dev || fb_helper->fbdev != info)
>> +		return NOTIFY_DONE;
>> +
>> +	sdrm = fb_helper->dev->dev_private;
>> +
>> +	if (sdrm && sdrm->fb_helper == fb_helper)
>> +		platform_device_del(to_platform_device(fb_helper->dev->dev));
>> +
>> +	return NOTIFY_DONE;
>> +}
> One problem this leaves behind is that registering of the new fbdev driver
> is too late - by that point we've already set up the entire driver,
> including modeset. If fbdev meanwhile does a dpms off or something like
> that all hell will break loose.

I don't understand how fbdev registration comes into play here. Drivers call
remove_conflicting_framebuffers very early so simpledrm is gone by the time
they register anything.

For simpledrm, fbdev doing blank/unblank is a no-op since fb_ops.fb_blank
is not implemented. So a fb_blank() just results in fbcon doing a
software blank.

> I think the only option is to add a new notifier chain for fbdev removal,
> called from remove_conflicting_framebuffers (even for CONFIG_FB=n, so need
> a fallback in core/fb_notify.c like with the other notifier I think). That
> would at least keep things working if fbdev is entirely disabled. Or have
> I entirely misunderstood how this works?

How about extending the new wrapper you just added. Something like this:

static int find_simpledrm_cb(struct device *dev, void *data)
{
     struct platform_device *pdev = to_platform_device(dev);
     char *name = data;

     DRM_INFO("Switching to %s from simpledrm\n", name);
     platform_device_del(pdev);

     return 0;
}

/* not sure where this function should go */
void drm_remove_simpledrm(const char *name)
{
     struct device_driver *drv;

     drv = driver_find("simpledrm", &platform_bus_type);
     if (drv)
         driver_for_each_device(drv, NULL, name, find_simpledrm_cb);

     /* or we can just unregister the driver instead */
     if (drv) {
         struct platform_driver *pdrv = to_platform_driver(drv);

         DRM_INFO("simpledrm is being removed by %s\n", name);
         platform_driver_unregister(pdrv);
     }
}

static inline int
drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a,
                           const char *name, bool primary)
{
     int ret;

     ret = remove_conflicting_framebuffers(a, name, primary);
     drm_remove_simpledrm(name);

     return ret;
}
#else
static inline int
drm_fb_helper_remove_conflicting_framebuffers(struct apertures_struct *a,
                           const char *name, bool primary)
{
     drm_remove_simpledrm(name);

     return 0;
}

This removes simpledrm unconditionally without looking at apertures_struct
though. Not sure if that is needed?


Noralf.

> Oh and another one, which we learned the hard way with i915: We probably
> need to get rid of vgacon too. And again we need to do that _before_ we
> start touching the hardware. I think simpledrm probably needs that too.
> But since simpledrm doesn't know on which pci device it sits it probably
> won't know if the same device is decoding vga signals, so no one knows how
> that is supposed to work :( One thing at the time probably.
> -Daniel
>
>> +
>> +static struct notifier_block sdrm_fbdev_event_notifier = {
>> +	.notifier_call  = sdrm_fbdev_event_notify,
>> +};
>> +
>> +void sdrm_fbdev_kickout_init(void)
>> +{
>> +	fb_register_client(&sdrm_fbdev_event_notifier);
>> +}
>> +
>> +void sdrm_fbdev_kickout_exit(void)
>> +{
>> +	fb_unregister_client(&sdrm_fbdev_event_notifier);
>> +}
>> --
>> 2.8.2
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers()
  2016-08-23 17:52     ` Noralf Trønnes
@ 2016-08-23 18:01       ` Daniel Vetter
  2016-08-23 19:22         ` Noralf Trønnes
  0 siblings, 1 reply; 19+ messages in thread
From: Daniel Vetter @ 2016-08-23 18:01 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, Linux Kernel Mailing List, devicetree

On Tue, Aug 23, 2016 at 7:52 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
>>> +static int sdrm_fbdev_event_notify(struct notifier_block *self,
>>> +                                  unsigned long action, void *data)
>>> +{
>>> +       struct sdrm_device *sdrm;
>>> +       struct fb_event *event = data;
>>> +       struct fb_info *info = event->info;
>>> +       struct drm_fb_helper *fb_helper = info->par;
>>> +
>>> +       if (action != FB_EVENT_FB_UNREGISTERED)
>>> +               return NOTIFY_DONE;
>>> +
>>> +       if (!fb_helper || !fb_helper->dev || fb_helper->fbdev != info)
>>> +               return NOTIFY_DONE;
>>> +
>>> +       sdrm = fb_helper->dev->dev_private;
>>> +
>>> +       if (sdrm && sdrm->fb_helper == fb_helper)
>>> +
>>> platform_device_del(to_platform_device(fb_helper->dev->dev));
>>> +
>>> +       return NOTIFY_DONE;
>>> +}
>>
>> One problem this leaves behind is that registering of the new fbdev driver
>> is too late - by that point we've already set up the entire driver,
>> including modeset. If fbdev meanwhile does a dpms off or something like
>> that all hell will break loose.
>
>
> I don't understand how fbdev registration comes into play here. Drivers call
> remove_conflicting_framebuffers very early so simpledrm is gone by the time
> they register anything.
>
> For simpledrm, fbdev doing blank/unblank is a no-op since fb_ops.fb_blank
> is not implemented. So a fb_blank() just results in fbcon doing a
> software blank.

Maybe my scenario wasn't entirely clear:
- prereq: fbdev emulation in drm is disabled
1. simpledrm loads and sets up the firmware fb
2. real driver loads, first calls
drm_fb_helper_remove_conflicting_framebuffer. Nothing happens because
CONFIG_FB=n.
3. real driver start loading, remapping the gart and what not else
4. something is drawn using fbcon, simplerdrm writes through the now
invalid mapping
-> BOOM

You have code to listen to the framebuffer registration notifier, but
I think even that happens way too late. Or at least I didn't spot any
code in remove_conflicting_framebuffers which would call down into
that notifier. Or maybe I entirely misunderstand your code ...

Wrt fixing: Just adding it to the recently added stub is of course
also a working solution.
-Daniel

PS: Can you pls review the 2 patches I submitted with you on cc? I
won't merge my own patches without proper review, so without that done
they're stuck.
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers()
  2016-08-23 18:01       ` Daniel Vetter
@ 2016-08-23 19:22         ` Noralf Trønnes
  0 siblings, 0 replies; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-23 19:22 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, Linux Kernel Mailing List, devicetree


Den 23.08.2016 20:01, skrev Daniel Vetter:
> On Tue, Aug 23, 2016 at 7:52 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
>>>> +static int sdrm_fbdev_event_notify(struct notifier_block *self,
>>>> +                                  unsigned long action, void *data)
>>>> +{
>>>> +       struct sdrm_device *sdrm;
>>>> +       struct fb_event *event = data;
>>>> +       struct fb_info *info = event->info;
>>>> +       struct drm_fb_helper *fb_helper = info->par;
>>>> +
>>>> +       if (action != FB_EVENT_FB_UNREGISTERED)
>>>> +               return NOTIFY_DONE;
>>>> +
>>>> +       if (!fb_helper || !fb_helper->dev || fb_helper->fbdev != info)
>>>> +               return NOTIFY_DONE;
>>>> +
>>>> +       sdrm = fb_helper->dev->dev_private;
>>>> +
>>>> +       if (sdrm && sdrm->fb_helper == fb_helper)
>>>> +
>>>> platform_device_del(to_platform_device(fb_helper->dev->dev));
>>>> +
>>>> +       return NOTIFY_DONE;
>>>> +}
>>> One problem this leaves behind is that registering of the new fbdev driver
>>> is too late - by that point we've already set up the entire driver,
>>> including modeset. If fbdev meanwhile does a dpms off or something like
>>> that all hell will break loose.
>>
>> I don't understand how fbdev registration comes into play here. Drivers call
>> remove_conflicting_framebuffers very early so simpledrm is gone by the time
>> they register anything.
>>
>> For simpledrm, fbdev doing blank/unblank is a no-op since fb_ops.fb_blank
>> is not implemented. So a fb_blank() just results in fbcon doing a
>> software blank.
> Maybe my scenario wasn't entirely clear:
> - prereq: fbdev emulation in drm is disabled
> 1. simpledrm loads and sets up the firmware fb
> 2. real driver loads, first calls
> drm_fb_helper_remove_conflicting_framebuffer. Nothing happens because
> CONFIG_FB=n.
> 3. real driver start loading, remapping the gart and what not else
> 4. something is drawn using fbcon, simplerdrm writes through the now
> invalid mapping
> -> BOOM

Yes CONFIG_FB=n is not covered, that's the drawback mentioned in the 
Kconfig.
However, who uses simpledrm without fbdev/fbcon when it shall be handed over
to a real hw-driver?
But yes, it's not a good stop gap solution, I like my other idea much 
better.

> You have code to listen to the framebuffer registration notifier, but
> I think even that happens way too late. Or at least I didn't spot any
> code in remove_conflicting_framebuffers which would call down into
> that notifier. Or maybe I entirely misunderstand your code ...

remove_conflicting_framebuffers unregisters the fbdev framebuffer.
sdrm_fbdev_event_notify detects that the framebuffer is being unregistered,
and deletes the platform device.

Some details:

do_remove_conflicting_framebuffers():
         for (i = 0 ; i < FB_MAX; i++) {
[...]
                         printk(KERN_INFO "fb: switching to %s from %s\n",
                                name, registered_fb[i]->fix.id);
                         ret = do_unregister_framebuffer(registered_fb[i]);

do_unregister_framebuffer(): short version
{
     console_lock();

         event.info = fb_info;
         ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event);
         unlock_fb_info(fb_info);
         console_unlock();

         pm_vt_switch_unregister(fb_info->dev);

         unlink_framebuffer(fb_info);

         registered_fb[i] = NULL;
         num_registered_fb--;

         event.info = fb_info;
         console_lock();
         fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);
/* at this point simpledrm has been deleted */
         console_unlock();

         /* this may free fb info */
         put_fb_info(fb_info);
         return 0;
}

> Wrt fixing: Just adding it to the recently added stub is of course
> also a working solution.

I actually like this better, because it's so straightforward and easy
to understand. The notifier solution is very convoluted and easy to
mess up. And it runs with the console lock held...

> -Daniel
>
> PS: Can you pls review the 2 patches I submitted with you on cc? I
> won't merge my own patches without proper review, so without that done
> they're stuck.

Ok.

I'll test the simple-helper plane patch tomorrow.
In simpledrm I first tried to send the vblank event only in pipe.update(),
but I had to do it in enable() and disable() as well to make that vblank
timeout go away.


Noralf.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-08-22 20:25 ` [PATCH v4 3/5] drm: add SimpleDRM driver Noralf Trønnes
  2016-08-23  6:17   ` Daniel Vetter
@ 2016-08-25 13:09   ` Rob Herring
  2016-08-25 18:42     ` Noralf Trønnes
  2016-09-01 23:48   ` David Herrmann
  2 siblings, 1 reply; 19+ messages in thread
From: Rob Herring @ 2016-08-25 13:09 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: dri-devel, devicetree, David Herrmann, linux-kernel, Luc Verhaegen

On Mon, Aug 22, 2016 at 3:25 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> The SimpleDRM driver binds to simple-framebuffer devices and provides a
> DRM/KMS API. It provides only a single CRTC+encoder+connector combination
> plus one initial mode.
>
> Userspace can create dumb-buffers which can be blit into the real
> framebuffer similar to UDL. No access to the real framebuffer is allowed
> (compared to earlier version of this driver) to avoid security issues.
> Furthermore, this way we can support arbitrary modes as long as we have a
> conversion-helper.
>
> The driver was originally written by David Herrmann in 2014.
> My main contribution is to make use of drm_simple_kms_helper and
> rework the probe path to avoid use of the deprecated drm_platform_init()
> and drm_driver.{load,unload}().
> Additions have also been made for later changes to the Device Tree binding
> document, like support for clocks, regulators and having the node under
> /chosen. This code was lifted verbatim from simplefb.c.
>
> Cc: dh.herrmann@gmail.com
> Cc: libv@skynet.be
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

[...]

> +       /* Count the number of regulator supplies */
> +       for_each_property_of_node(np, prop) {
> +               p = strstr(prop->name, SUPPLY_SUFFIX);
> +               if (p && p != prop->name)
> +                       count++;
> +       }

The regulator API should have functions for this rather than open coding it.

> +
> +       if (!count)
> +               return 0;
> +
> +       sdrm->regulators = devm_kcalloc(&pdev->dev, count,
> +                                       sizeof(struct regulator *),
> +                                       GFP_KERNEL);
> +       if (!sdrm->regulators)
> +               return -ENOMEM;
> +
> +       /* Get all the regulators */
> +       for_each_property_of_node(np, prop) {
> +               char name[32]; /* 32 is max size of property name */
> +
> +               p = strstr(prop->name, SUPPLY_SUFFIX);
> +               if (!p || p == prop->name)
> +                       continue;

This too.

> +
> +               strlcpy(name, prop->name,
> +                       strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1);
> +               regulator = devm_regulator_get_optional(&pdev->dev, name);

[...]

> +       if (IS_ENABLED(CONFIG_OF_ADDRESS) && of_chosen) {
> +               struct device_node *np;
> +
> +               for_each_child_of_node(of_chosen, np) {
> +                       if (of_device_is_compatible(np, "simple-framebuffer"))

Rather than exporting of_chosen, this whole chunk can be replaced with
a of_find_compatible_node call. Yes, that would match if
simple-framebuffer exists somewhere else in the DT, but it is not the
kernel's job to do DT validation.

> +                               of_platform_device_create(np, NULL, NULL);
> +               }
> +       }

Rob

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-08-25 13:09   ` Rob Herring
@ 2016-08-25 18:42     ` Noralf Trønnes
  0 siblings, 0 replies; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-25 18:42 UTC (permalink / raw)
  To: Rob Herring
  Cc: dri-devel, devicetree, David Herrmann, linux-kernel, Luc Verhaegen


Den 25.08.2016 15:09, skrev Rob Herring:
> On Mon, Aug 22, 2016 at 3:25 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
>> The SimpleDRM driver binds to simple-framebuffer devices and provides a
>> DRM/KMS API. It provides only a single CRTC+encoder+connector combination
>> plus one initial mode.
>>
>> Userspace can create dumb-buffers which can be blit into the real
>> framebuffer similar to UDL. No access to the real framebuffer is allowed
>> (compared to earlier version of this driver) to avoid security issues.
>> Furthermore, this way we can support arbitrary modes as long as we have a
>> conversion-helper.
>>
>> The driver was originally written by David Herrmann in 2014.
>> My main contribution is to make use of drm_simple_kms_helper and
>> rework the probe path to avoid use of the deprecated drm_platform_init()
>> and drm_driver.{load,unload}().
>> Additions have also been made for later changes to the Device Tree binding
>> document, like support for clocks, regulators and having the node under
>> /chosen. This code was lifted verbatim from simplefb.c.
>>
>> Cc: dh.herrmann@gmail.com
>> Cc: libv@skynet.be
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> [...]
>
>> +       /* Count the number of regulator supplies */
>> +       for_each_property_of_node(np, prop) {
>> +               p = strstr(prop->name, SUPPLY_SUFFIX);
>> +               if (p && p != prop->name)
>> +                       count++;
>> +       }
> The regulator API should have functions for this rather than open coding it.

I couldn't find anything that matches this usecase. There are regulator
bulk functions, but they require the names to be known in advance.
And I don't know how many regulators there are nor their names.

>From Documentation/devicetree/bindings/display/simple-framebuffer.txt:

- *-supply : Any number of regulators used by the framebuffer. These should
          be named according to the names in the device's design.

>> +
>> +       if (!count)
>> +               return 0;
>> +
>> +       sdrm->regulators = devm_kcalloc(&pdev->dev, count,
>> +                                       sizeof(struct regulator *),
>> +                                       GFP_KERNEL);
>> +       if (!sdrm->regulators)
>> +               return -ENOMEM;
>> +
>> +       /* Get all the regulators */
>> +       for_each_property_of_node(np, prop) {
>> +               char name[32]; /* 32 is max size of property name */
>> +
>> +               p = strstr(prop->name, SUPPLY_SUFFIX);
>> +               if (!p || p == prop->name)
>> +                       continue;
> This too.
>
>> +
>> +               strlcpy(name, prop->name,
>> +                       strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1);
>> +               regulator = devm_regulator_get_optional(&pdev->dev, name);
> [...]
>
>> +       if (IS_ENABLED(CONFIG_OF_ADDRESS) && of_chosen) {
>> +               struct device_node *np;
>> +
>> +               for_each_child_of_node(of_chosen, np) {
>> +                       if (of_device_is_compatible(np, "simple-framebuffer"))
> Rather than exporting of_chosen, this whole chunk can be replaced with
> a of_find_compatible_node call. Yes, that would match if
> simple-framebuffer exists somewhere else in the DT, but it is not the
> kernel's job to do DT validation.

This seems to do the job:

     /*
      * The binding doc states that simplefb nodes should be sub-nodes of
      * chosen and that older devicetree files can have them in a different
      * place. of_platform_device_create() just returns NULL if a device
      * has already been created for the node.
      */
     for_each_compatible_node(np, NULL, "simple-framebuffer")
         of_platform_device_create(np, NULL, NULL);


Noralf.

>> +                               of_platform_device_create(np, NULL, NULL);
>> +               }
>> +       }
> Rob
>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-08-23  6:17   ` Daniel Vetter
@ 2016-08-25 22:11     ` Noralf Trønnes
  2016-08-26 14:40       ` Daniel Vetter
  0 siblings, 1 reply; 19+ messages in thread
From: Noralf Trønnes @ 2016-08-25 22:11 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, devicetree, linux-kernel


Den 23.08.2016 08:17, skrev Daniel Vetter:
> On Mon, Aug 22, 2016 at 10:25:23PM +0200, Noralf Trønnes wrote:
>> The SimpleDRM driver binds to simple-framebuffer devices and provides a
>> DRM/KMS API. It provides only a single CRTC+encoder+connector combination
>> plus one initial mode.
>>
>> Userspace can create dumb-buffers which can be blit into the real
>> framebuffer similar to UDL. No access to the real framebuffer is allowed
>> (compared to earlier version of this driver) to avoid security issues.
>> Furthermore, this way we can support arbitrary modes as long as we have a
>> conversion-helper.
>>
>> The driver was originally written by David Herrmann in 2014.
>> My main contribution is to make use of drm_simple_kms_helper and
>> rework the probe path to avoid use of the deprecated drm_platform_init()
>> and drm_driver.{load,unload}().
>> Additions have also been made for later changes to the Device Tree binding
>> document, like support for clocks, regulators and having the node under
>> /chosen. This code was lifted verbatim from simplefb.c.
>>
>> Cc: dh.herrmann@gmail.com
>> Cc: libv@skynet.be
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---

<snip>

>> +static const struct file_operations sdrm_drm_fops = {
>> +	.owner = THIS_MODULE,
>> +	.open = drm_open,
>> +	.mmap = sdrm_drm_mmap,
>> +	.poll = drm_poll,
>> +	.read = drm_read,
>> +	.unlocked_ioctl = drm_ioctl,
>> +	.release = drm_release,
>> +#ifdef CONFIG_COMPAT
>> +	.compat_ioctl = drm_compat_ioctl,
>> +#endif
>> +	.llseek = noop_llseek,
>> +};
>> +
>> +static const struct vm_operations_struct sdrm_gem_vm_ops = {
>> +	.fault = sdrm_gem_fault,
>> +	.open = drm_gem_vm_open,
>> +	.close = drm_gem_vm_close,
>> +};
>> +

<snip>

>> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
>> new file mode 100644
>> index 0000000..8cced80
>> --- /dev/null
>> +++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
>> @@ -0,0 +1,202 @@
>> +/*
>> + * SimpleDRM firmware framebuffer driver
>> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
>> + *
>> + * 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 <linux/errno.h>
>> +#include <linux/mm.h>
>> +#include <linux/slab.h>
>> +#include <linux/vmalloc.h>
>> +
>> +#include "simpledrm.h"
>> +
>> +struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
>> +					      size_t size)
>> +{
>> +	struct sdrm_gem_object *obj;
>> +
>> +	WARN_ON(!size || (size & ~PAGE_MASK) != 0);
>> +
>> +	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
>> +	if (!obj)
>> +		return NULL;
>> +
>> +	if (drm_gem_object_init(ddev, &obj->base, size)) {
>> +		kfree(obj);
>> +		return NULL;
>> +	}
>> +
>> +	return obj;
>> +}
>> +
>> +int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
>> +		     struct drm_mode_create_dumb *args)
>> +{
>> +	struct sdrm_gem_object *obj;
>> +	int r;
>> +
>> +	/* overflow checks are done by DRM core */
>> +	args->pitch = (args->bpp + 7) / 8 * args->width;
>> +	args->size = PAGE_ALIGN(args->pitch * args->height);
>> +
>> +	obj = sdrm_gem_alloc_object(ddev, args->size);
>> +	if (!obj)
>> +		return -ENOMEM;
>> +
>> +	r = drm_gem_handle_create(dfile, &obj->base, &args->handle);
>> +	if (r) {
>> +		drm_gem_object_unreference_unlocked(&obj->base);
>> +		return r;
>> +	}
>> +
>> +	/* handle owns a reference */
>> +	drm_gem_object_unreference_unlocked(&obj->base);
>> +
>> +	return 0;
>> +}
>> +
>> +int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
>> +{
>> +	int ret;
>> +
>> +	ret = drm_gem_mmap(filp, vma);
>> +	if (ret)
>> +		return ret;
>> +
>> +	vma->vm_flags &= ~VM_PFNMAP;
>> +	vma->vm_flags |= VM_MIXEDMAP;
>> +	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> Hm, this is still the hand-rolled mmap support. Did my more detailed plan
> not work, now that you've switched to more native gem objects? Doing it
> that way would allow us to remove all the hand-rolled fault handling
> (especially sdrm_gem_fault), which is think would be nice.
>
> I know, udl doesn't do it that way, not sure exactly why.

I'm really walking in the dark here. I have deleted drm_driver.gem_vm_ops
and used this function:

int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
{
     struct drm_file *priv = filp->private_data;
     struct drm_device *dev = priv->minor->dev;
     struct drm_gem_object *obj = NULL;
     struct drm_vma_offset_node *node;
     int ret;

     drm_vma_offset_lock_lookup(dev->vma_offset_manager);
     node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
                           vma->vm_pgoff,
                           vma_pages(vma));
     if (likely(node)) {
         obj = container_of(node, struct drm_gem_object, vma_node);
         /*
         * When the object is being freed, after it hits 0-refcnt it
         * proceeds to tear down the object. In the process it will
         * attempt to remove the VMA offset and so acquire this
         * mgr->vm_lock.  Therefore if we find an object with a 0-refcnt
         * that matches our range, we know it is in the process of being
         * destroyed and will be freed as soon as we release the lock -
         * so we have to check for the 0-refcnted object and treat it as
         * invalid.
         */
         if (!kref_get_unless_zero(&obj->refcount))
             obj = NULL;
     }
     drm_vma_offset_unlock_lookup(dev->vma_offset_manager);

     if (!obj)
         return -EINVAL;

     if (!drm_vma_node_is_allowed(node, filp)) {
         drm_gem_object_unreference_unlocked(obj);
         return -EACCES;
     }

     /* redirect to shmem mmap */
     vma->vm_file = obj->filp;
     vma->vm_pgoff = 0;

     ret = obj->filp->f_op->mmap(obj->filp, vma);

     drm_gem_object_unreference_unlocked(obj);

     return ret;
}

But that works only partly. Using modetest I get a picture, but fbdev 
doesn't return.
Turning on drm debug the two variants are identical up to 
DRM_IOCTL_MODE_DESTROY_DUMB.

The shmem mmap version:
[identical]
[   74.939660] [drm:drm_ioctl] pid=721, dev=0xe200, auth=1, 
DRM_IOCTL_MODE_DESTROY_DUMB
And nothing more

Whereas the working one gives me this:
[identical]
[   70.373029] [drm:drm_ioctl] pid=721, dev=0xe200, auth=1, 
DRM_IOCTL_MODE_DESTROY_DUMB
[   70.393401] [drm:drm_release] open_count = 1
[   70.393429] [drm:drm_release] pid = 721, device = 0xe200, open_count = 1
[   70.393468] [drm:drm_lastclose]
[   70.393501] [drm:drm_atomic_state_init] Allocated atomic state dad61e00
[   70.393521] [drm:drm_atomic_get_plane_state] Added [PLANE:24:plane-0] 
dad61e40 state to dad61e00
[   70.393543] [drm:drm_atomic_get_crtc_state] Added [CRTC:25:crtc-0] 
dad73a00 state to dad61e00
[   70.393588] [drm:drm_atomic_set_mode_for_crtc] Set [MODE:1824x984] 
for CRTC state dad73a00
[   70.393604] [drm:drm_atomic_set_crtc_for_plane] Link plane state 
dad61e40 to [CRTC:25:crtc-0]
[   70.393619] [drm:drm_mode_object_reference] OBJ ID: 29 (1)
[   70.393629] [drm:drm_atomic_set_fb_for_plane] Set [FB:29] for plane 
state dad61e40
[   70.393643] [drm:drm_atomic_add_affected_connectors] Adding all 
current connectors for [CRTC:25:crtc-0] to dad61e00
[   70.393662] [drm:drm_mode_object_reference] OBJ ID: 23 (2)
[   70.393674] [drm:drm_atomic_get_connector_state] Added [CONNECTOR:23] 
dad613c0 state to dad61e00
[   70.393835] [drm:drm_mode_object_reference] OBJ ID: 23 (3)
[   70.393848] [drm:drm_atomic_set_crtc_for_connector] Link connector 
state dad613c0 to [CRTC:25:crtc-0]
[   70.393859] [drm:drm_atomic_check_only] checking dad61e00
[   70.393873] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] 
mode changed
[   70.393881] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] 
enable changed
[   70.403886] [drm:update_connector_routing] Updating routing for 
[CONNECTOR:23:Virtual-1]
[   70.403916] [drm:update_connector_routing] [CONNECTOR:23:Virtual-1] 
using [ENCODER:26:None-26] on [CRTC:25:crtc-0]
[   70.403926] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] 
active changed
[   70.403956] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] 
needs all connectors, enable: y, active: y
[   70.403972] [drm:drm_atomic_add_affected_connectors] Adding all 
current connectors for [CRTC:25:crtc-0] to dad61e00
[   70.404006] [drm:drm_atomic_commit] commiting dad61e00
[   70.404043] [drm:crtc_set_mode] modeset on [ENCODER:26:None-26]
[   70.422427] [drm:drm_atomic_helper_commit_modeset_enables] enabling 
[CRTC:25:crtc-0]
[   70.422465] [drm:drm_atomic_helper_commit_modeset_enables] enabling 
[ENCODER:26:None-26]
[   70.422490] [drm:drm_atomic_state_default_clear] Clearing atomic 
state dad61e00
[   70.422504] [drm:drm_mode_object_unreference] OBJ ID: 23 (4)
[   70.422519] [drm:drm_atomic_state_free] Freeing atomic state dad61e00
[   70.422532] [drm:drm_mode_object_reference] OBJ ID: 29 (2)
[   70.422546] [drm:drm_lastclose] driver lastclose completed


Noralf.


>> +
>> +	return 0;
>> +}
>> +
>> +int sdrm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
>> +{
>> +	struct drm_gem_object *gobj = vma->vm_private_data;
>> +	struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
>> +	pgoff_t offset;
>> +	int ret;
>> +
>> +	if (!obj->pages)
>> +		return VM_FAULT_SIGBUS;
>> +
>> +	offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >>
>> +		 PAGE_SHIFT;
>> +
>> +	ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address,
>> +			     obj->pages[offset]);
>> +	switch (ret) {
>> +	case -EAGAIN:
>> +	case 0:
>> +	case -ERESTARTSYS:
>> +	case -EINTR:
>> +	case -EBUSY:
>> +		return VM_FAULT_NOPAGE;
>> +
>> +	case -ENOMEM:
>> +		return VM_FAULT_OOM;
>> +	}
>> +
>> +	return VM_FAULT_SIGBUS;
>> +}
>> +
>> +int sdrm_gem_get_pages(struct sdrm_gem_object *obj)
>> +{
>> +	struct page **pages;
>> +
>> +	if (obj->pages)
>> +		return 0;
>> +
>> +	pages = drm_gem_get_pages(&obj->base);
>> +	if (IS_ERR(pages))
>> +		return PTR_ERR(pages);
>> +
>> +	obj->pages = pages;
>> +
>> +	return 0;
>> +}
>> +
>> +static void sdrm_gem_put_pages(struct sdrm_gem_object *obj)
>> +{
>> +	if (!obj->pages)
>> +		return;
>> +
>> +	drm_gem_put_pages(&obj->base, obj->pages, false, false);
>> +	obj->pages = NULL;
>> +}
>> +
>> +int sdrm_gem_vmap(struct sdrm_gem_object *obj)
>> +{
>> +	int page_count = obj->base.size / PAGE_SIZE;
>> +	int ret;
>> +
>> +	if (obj->vmapping)
>> +		return 0;
>> +
>> +	ret = sdrm_gem_get_pages(obj);
>> +	if (ret)
>> +		return ret;
>> +
>> +	obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
>> +	if (!obj->vmapping)
>> +		return -ENOMEM;
>> +
>> +	return 0;
>> +}
>> +
>> +static void sdrm_gem_vunmap(struct sdrm_gem_object *obj)
>> +{
>> +	vunmap(obj->vmapping);
>> +	obj->vmapping = NULL;
>> +
>> +	sdrm_gem_put_pages(obj);
>> +}
>> +
>> +void sdrm_gem_free_object(struct drm_gem_object *gobj)
>> +{
>> +	struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
>> +
>> +	if (obj->vmapping)
>> +		sdrm_gem_vunmap(obj);
>> +
>> +	if (obj->pages)
>> +		sdrm_gem_put_pages(obj);
>> +
>> +	drm_gem_object_release(gobj);
>> +	kfree(obj);
>> +}
>> +
>> +int sdrm_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
>> +			 uint32_t handle, uint64_t *offset)
>> +{
>> +	struct sdrm_gem_object *obj;
>> +	struct drm_gem_object *gobj;
>> +	int ret;
>> +
>> +	gobj = drm_gem_object_lookup(dfile, handle);
>> +	if (!gobj)
>> +		return -ENOENT;
>> +
>> +	obj = to_sdrm_bo(gobj);
>> +
>> +	ret = sdrm_gem_get_pages(obj);
>> +	if (ret)
>> +		goto out_unref;
>> +
>> +	ret = drm_gem_create_mmap_offset(gobj);
>> +	if (ret)
>> +		goto out_unref;
>> +
>> +	*offset = drm_vma_node_offset_addr(&gobj->vma_node);
>> +
>> +out_unref:
>> +	drm_gem_object_unreference_unlocked(gobj);
>> +
>> +	return ret;
>> +}

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-08-25 22:11     ` Noralf Trønnes
@ 2016-08-26 14:40       ` Daniel Vetter
  0 siblings, 0 replies; 19+ messages in thread
From: Daniel Vetter @ 2016-08-26 14:40 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, devicetree, Linux Kernel Mailing List

On Fri, Aug 26, 2016 at 12:11 AM, Noralf Trønnes <noralf@tronnes.org> wrote:
> I'm really walking in the dark here. I have deleted drm_driver.gem_vm_ops
> and used this function:
>
> int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
> {
>     struct drm_file *priv = filp->private_data;
>     struct drm_device *dev = priv->minor->dev;
>     struct drm_gem_object *obj = NULL;
>     struct drm_vma_offset_node *node;
>     int ret;
>
>     drm_vma_offset_lock_lookup(dev->vma_offset_manager);
>     node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
>                           vma->vm_pgoff,
>                           vma_pages(vma));
>     if (likely(node)) {
>         obj = container_of(node, struct drm_gem_object, vma_node);
>         /*
>         * When the object is being freed, after it hits 0-refcnt it
>         * proceeds to tear down the object. In the process it will
>         * attempt to remove the VMA offset and so acquire this
>         * mgr->vm_lock.  Therefore if we find an object with a 0-refcnt
>         * that matches our range, we know it is in the process of being
>         * destroyed and will be freed as soon as we release the lock -
>         * so we have to check for the 0-refcnted object and treat it as
>         * invalid.
>         */
>         if (!kref_get_unless_zero(&obj->refcount))
>             obj = NULL;
>     }
>     drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
>
>     if (!obj)
>         return -EINVAL;
>
>     if (!drm_vma_node_is_allowed(node, filp)) {
>         drm_gem_object_unreference_unlocked(obj);
>         return -EACCES;
>     }
>
>     /* redirect to shmem mmap */
>     vma->vm_file = obj->filp;
>     vma->vm_pgoff = 0;
>
>     ret = obj->filp->f_op->mmap(obj->filp, vma);
>
>     drm_gem_object_unreference_unlocked(obj);
>
>     return ret;
> }

Yup, this looks good.

> But that works only partly. Using modetest I get a picture, but fbdev
> doesn't return.
> Turning on drm debug the two variants are identical up to
> DRM_IOCTL_MODE_DESTROY_DUMB.
>
> The shmem mmap version:
> [identical]
> [   74.939660] [drm:drm_ioctl] pid=721, dev=0xe200, auth=1,
> DRM_IOCTL_MODE_DESTROY_DUMB
> And nothing more

Hm, what does your fbdev support code now look like? fbdev doesn't do
paging, so for that you still need the get_pages(); vmap; thing at
setup time, and then when you tear down the fbdev stuff a vunmap();
and put_pages();

And of course the dirty stuff all needs to be wired up, but since it
works for modeset I think we can assume it's in good shape.
-Daniel

> Whereas the working one gives me this:
> [identical]
> [   70.373029] [drm:drm_ioctl] pid=721, dev=0xe200, auth=1,
> DRM_IOCTL_MODE_DESTROY_DUMB
> [   70.393401] [drm:drm_release] open_count = 1
> [   70.393429] [drm:drm_release] pid = 721, device = 0xe200, open_count = 1
> [   70.393468] [drm:drm_lastclose]
> [   70.393501] [drm:drm_atomic_state_init] Allocated atomic state dad61e00
> [   70.393521] [drm:drm_atomic_get_plane_state] Added [PLANE:24:plane-0]
> dad61e40 state to dad61e00
> [   70.393543] [drm:drm_atomic_get_crtc_state] Added [CRTC:25:crtc-0]
> dad73a00 state to dad61e00
> [   70.393588] [drm:drm_atomic_set_mode_for_crtc] Set [MODE:1824x984] for
> CRTC state dad73a00
> [   70.393604] [drm:drm_atomic_set_crtc_for_plane] Link plane state dad61e40
> to [CRTC:25:crtc-0]
> [   70.393619] [drm:drm_mode_object_reference] OBJ ID: 29 (1)
> [   70.393629] [drm:drm_atomic_set_fb_for_plane] Set [FB:29] for plane state
> dad61e40
> [   70.393643] [drm:drm_atomic_add_affected_connectors] Adding all current
> connectors for [CRTC:25:crtc-0] to dad61e00
> [   70.393662] [drm:drm_mode_object_reference] OBJ ID: 23 (2)
> [   70.393674] [drm:drm_atomic_get_connector_state] Added [CONNECTOR:23]
> dad613c0 state to dad61e00
> [   70.393835] [drm:drm_mode_object_reference] OBJ ID: 23 (3)
> [   70.393848] [drm:drm_atomic_set_crtc_for_connector] Link connector state
> dad613c0 to [CRTC:25:crtc-0]
> [   70.393859] [drm:drm_atomic_check_only] checking dad61e00
> [   70.393873] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] mode
> changed
> [   70.393881] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] enable
> changed
> [   70.403886] [drm:update_connector_routing] Updating routing for
> [CONNECTOR:23:Virtual-1]
> [   70.403916] [drm:update_connector_routing] [CONNECTOR:23:Virtual-1] using
> [ENCODER:26:None-26] on [CRTC:25:crtc-0]
> [   70.403926] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] active
> changed
> [   70.403956] [drm:drm_atomic_helper_check_modeset] [CRTC:25:crtc-0] needs
> all connectors, enable: y, active: y
> [   70.403972] [drm:drm_atomic_add_affected_connectors] Adding all current
> connectors for [CRTC:25:crtc-0] to dad61e00
> [   70.404006] [drm:drm_atomic_commit] commiting dad61e00
> [   70.404043] [drm:crtc_set_mode] modeset on [ENCODER:26:None-26]
> [   70.422427] [drm:drm_atomic_helper_commit_modeset_enables] enabling
> [CRTC:25:crtc-0]
> [   70.422465] [drm:drm_atomic_helper_commit_modeset_enables] enabling
> [ENCODER:26:None-26]
> [   70.422490] [drm:drm_atomic_state_default_clear] Clearing atomic state
> dad61e00
> [   70.422504] [drm:drm_mode_object_unreference] OBJ ID: 23 (4)
> [   70.422519] [drm:drm_atomic_state_free] Freeing atomic state dad61e00
> [   70.422532] [drm:drm_mode_object_reference] OBJ ID: 29 (2)
> [   70.422546] [drm:drm_lastclose] driver lastclose completed
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-08-22 20:25 ` [PATCH v4 3/5] drm: add SimpleDRM driver Noralf Trønnes
  2016-08-23  6:17   ` Daniel Vetter
  2016-08-25 13:09   ` Rob Herring
@ 2016-09-01 23:48   ` David Herrmann
  2016-09-02  6:11     ` Daniel Vetter
  2 siblings, 1 reply; 19+ messages in thread
From: David Herrmann @ 2016-09-01 23:48 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: dri-devel, robh, devicetree, linux-kernel, Luc Verhaegen

Hey

On Mon, Aug 22, 2016 at 10:25 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> The SimpleDRM driver binds to simple-framebuffer devices and provides a
> DRM/KMS API. It provides only a single CRTC+encoder+connector combination
> plus one initial mode.
>
> Userspace can create dumb-buffers which can be blit into the real
> framebuffer similar to UDL. No access to the real framebuffer is allowed
> (compared to earlier version of this driver) to avoid security issues.
> Furthermore, this way we can support arbitrary modes as long as we have a
> conversion-helper.
>
> The driver was originally written by David Herrmann in 2014.
> My main contribution is to make use of drm_simple_kms_helper and
> rework the probe path to avoid use of the deprecated drm_platform_init()
> and drm_driver.{load,unload}().
> Additions have also been made for later changes to the Device Tree binding
> document, like support for clocks, regulators and having the node under
> /chosen. This code was lifted verbatim from simplefb.c.
>
> Cc: dh.herrmann@gmail.com
> Cc: libv@skynet.be
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>
> Changes from version 3:
> - Reworked gem code to match udl
> - Dropped PRIME support
> - Dropped dirty_info_property, it's gone
> - Don't use drm_device.platformdev it's deprecated
> - Remove struct sdrm_device #ifdef's
> - Split out sdrm_fb_init() from sdrm_fb_create(), needed by fbdev code
> - Simplify drm_clip validation by extending the check in
>   sdrm_dirty() and drop the one in sdrm_blit()
> - Removed sdrm_dirty_all_unlocked() which was unused.
>
> Changes from version 2:
> - Remove superfluos module.h includes
> - Move includes from header to source files
> - Set plane.fb before flushing in pipe update, or else the previous one
>   gets flushed
> - Added check for vblank event in sdrm_display_pipe_update()
>
> Changes from version 1:
> - Move platform_set_drvdata() before drm_dev_register()
> - Remove drm_legacy_mmap() call.
> - Set mode_config.{min,max}_{width,height} to the actual dimensions
>   of the native framebuffer
> - Remove plane positioning since it won't work with the simple display pipe,
>   meaning sdrm_display_pipe_check() isn't necessary either
> - Support the additions to the Device Tree binding document, including
>   clocks, regulators and having the node under /chosen
>
> Changes from previous version:
> - Remove FB_SIMPLE=n dependency to avoid kconfig recursive error
> - Changed module name to match kconfig help text: sdrm -> simpledrm
> - Use drm_simple_display_pipe
> - Replace deprecated drm_platform_init()
> - sdrm_dumb_create(): drm_gem_object_unreference() -> *_unlocked()
> - sdrm_dumb_map_offset(): drm_gem_object_lookup() remove drm_device parameter
> - sdrm_drm_mmap() changes:
>   Remove struct_mutex locking
>   Add drm_vma_offset_{lock,unlock}_lookup()
>   drm_mmap() -> drm_legacy_mmap()
> - dma_buf_begin_cpu_access() doesn't require start and length anymore
> - Use drm_cvt_mode() instead of open coding a mode
> - Fix format conversion. In the intermediate step, store the 8/6/5 bit color
>   value in the upper part of the 16-bit color variable, not the lower.
> - Support clips == NULL in sdrm_dirty()
> - Set mode_config.preferred_depth
> - Attach mode_config.dirty_info_property to connector
>
>  drivers/gpu/drm/Kconfig                      |   2 +
>  drivers/gpu/drm/Makefile                     |   1 +
>  drivers/gpu/drm/simpledrm/Kconfig            |  19 +
>  drivers/gpu/drm/simpledrm/Makefile           |   4 +
>  drivers/gpu/drm/simpledrm/simpledrm.h        |  86 +++++
>  drivers/gpu/drm/simpledrm/simpledrm_damage.c | 235 ++++++++++++
>  drivers/gpu/drm/simpledrm/simpledrm_drv.c    | 543 +++++++++++++++++++++++++++
>  drivers/gpu/drm/simpledrm/simpledrm_gem.c    | 202 ++++++++++
>  drivers/gpu/drm/simpledrm/simpledrm_kms.c    | 234 ++++++++++++
>  9 files changed, 1326 insertions(+)
>  create mode 100644 drivers/gpu/drm/simpledrm/Kconfig
>  create mode 100644 drivers/gpu/drm/simpledrm/Makefile
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm.h
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_damage.c
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_drv.c
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_gem.c
>  create mode 100644 drivers/gpu/drm/simpledrm/simpledrm_kms.c
>
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index fc35731..a54cc8d 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -290,3 +290,5 @@ source "drivers/gpu/drm/arc/Kconfig"
>  source "drivers/gpu/drm/hisilicon/Kconfig"
>
>  source "drivers/gpu/drm/mediatek/Kconfig"
> +
> +source "drivers/gpu/drm/simpledrm/Kconfig"
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index e3dba6f..eba32ad 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -83,3 +83,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
>  obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
>  obj-$(CONFIG_DRM_ARCPGU)+= arc/
>  obj-y                  += hisilicon/
> +obj-$(CONFIG_DRM_SIMPLEDRM) += simpledrm/
> diff --git a/drivers/gpu/drm/simpledrm/Kconfig b/drivers/gpu/drm/simpledrm/Kconfig
> new file mode 100644
> index 0000000..f45b25d
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/Kconfig
> @@ -0,0 +1,19 @@
> +config DRM_SIMPLEDRM
> +       tristate "Simple firmware framebuffer DRM driver"
> +       depends on DRM
> +       select DRM_KMS_HELPER
> +       help
> +         SimpleDRM can run on all systems with pre-initialized graphics
> +         hardware. It uses a framebuffer that was initialized during
> +         firmware boot. No page-flipping, modesetting or other advanced
> +         features are available. However, other DRM drivers can be loaded
> +         later and take over from SimpleDRM if they provide real hardware
> +         support.
> +
> +         SimpleDRM supports "simple-framebuffer" DeviceTree objects and
> +         compatible platform framebuffers.
> +
> +         If unsure, say Y.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called simpledrm.
> diff --git a/drivers/gpu/drm/simpledrm/Makefile b/drivers/gpu/drm/simpledrm/Makefile
> new file mode 100644
> index 0000000..f6a62dc
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/Makefile
> @@ -0,0 +1,4 @@
> +simpledrm-y := simpledrm_drv.o simpledrm_kms.o simpledrm_gem.o \
> +               simpledrm_damage.o
> +
> +obj-$(CONFIG_DRM_SIMPLEDRM) := simpledrm.o
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm.h b/drivers/gpu/drm/simpledrm/simpledrm.h
> new file mode 100644
> index 0000000..0739581
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm.h
> @@ -0,0 +1,86 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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.
> + */
> +
> +#ifndef SDRM_DRV_H
> +#define SDRM_DRV_H
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_gem.h>
> +#include <drm/drm_simple_kms_helper.h>
> +
> +struct simplefb_format;
> +struct regulator;
> +struct clk;
> +
> +struct sdrm_device {
> +       struct drm_device *ddev;
> +       struct drm_simple_display_pipe pipe;
> +       struct drm_connector conn;
> +
> +       /* framebuffer information */
> +       const struct simplefb_format *fb_sformat;
> +       u32 fb_format;
> +       u32 fb_width;
> +       u32 fb_height;
> +       u32 fb_stride;
> +       u32 fb_bpp;
> +       unsigned long fb_base;
> +       unsigned long fb_size;
> +       void *fb_map;
> +
> +       unsigned int clk_count;
> +       struct clk **clks;
> +
> +       u32 regulator_count;
> +       struct regulator **regulators;
> +};
> +
> +int sdrm_drm_modeset_init(struct sdrm_device *sdrm);
> +
> +int sdrm_dirty(struct drm_framebuffer *fb,
> +              struct drm_file *file,
> +              unsigned int flags, unsigned int color,
> +              struct drm_clip_rect *clips,
> +              unsigned int num_clips);
> +int sdrm_dirty_all_locked(struct sdrm_device *sdrm);
> +
> +struct sdrm_gem_object {
> +       struct drm_gem_object base;
> +       struct page **pages;
> +       void *vmapping;
> +};
> +
> +#define to_sdrm_bo(x) container_of(x, struct sdrm_gem_object, base)
> +
> +int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma);
> +int sdrm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
> +int sdrm_gem_vmap(struct sdrm_gem_object *sobj);
> +
> +struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
> +                                             size_t size);
> +void sdrm_gem_free_object(struct drm_gem_object *obj);
> +
> +int sdrm_dumb_create(struct drm_file *file_priv, struct drm_device *ddev,
> +                    struct drm_mode_create_dumb *arg);
> +int sdrm_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev,
> +                        uint32_t handle, uint64_t *offset);
> +
> +struct sdrm_framebuffer {
> +       struct drm_framebuffer base;
> +       struct sdrm_gem_object *obj;
> +};
> +
> +#define to_sdrm_fb(x) container_of(x, struct sdrm_framebuffer, base)
> +
> +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);
> +
> +#endif /* SDRM_DRV_H */
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_damage.c b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
> new file mode 100644
> index 0000000..52c845f
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_damage.c
> @@ -0,0 +1,235 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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 <asm/unaligned.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_crtc.h>
> +#include <linux/dma-buf.h>
> +#include <linux/kernel.h>
> +#include <linux/string.h>
> +
> +#include "simpledrm.h"
> +
> +static inline void sdrm_put(u8 *dst, u32 four_cc, u16 r, u16 g, u16 b)
> +{
> +       switch (four_cc) {
> +       case DRM_FORMAT_RGB565:
> +               r >>= 11;
> +               g >>= 10;
> +               b >>= 11;
> +               put_unaligned((u16)((r << 11) | (g << 5) | b), (u16 *)dst);
> +               break;
> +       case DRM_FORMAT_XRGB1555:
> +       case DRM_FORMAT_ARGB1555:
> +               r >>= 11;
> +               g >>= 11;
> +               b >>= 11;
> +               put_unaligned((u16)((r << 10) | (g << 5) | b), (u16 *)dst);
> +               break;
> +       case DRM_FORMAT_RGB888:
> +               r >>= 8;
> +               g >>= 8;
> +               b >>= 8;
> +#ifdef __LITTLE_ENDIAN
> +               dst[2] = r;
> +               dst[1] = g;
> +               dst[0] = b;
> +#elif defined(__BIG_ENDIAN)
> +               dst[0] = r;
> +               dst[1] = g;
> +               dst[2] = b;
> +#endif
> +               break;
> +       case DRM_FORMAT_XRGB8888:
> +       case DRM_FORMAT_ARGB8888:
> +               r >>= 8;
> +               g >>= 8;
> +               b >>= 8;
> +               put_unaligned((u32)((r << 16) | (g << 8) | b), (u32 *)dst);
> +               break;
> +       case DRM_FORMAT_ABGR8888:
> +               r >>= 8;
> +               g >>= 8;
> +               b >>= 8;
> +               put_unaligned((u32)((b << 16) | (g << 8) | r), (u32 *)dst);
> +               break;
> +       case DRM_FORMAT_XRGB2101010:
> +       case DRM_FORMAT_ARGB2101010:
> +               r >>= 4;
> +               g >>= 4;
> +               b >>= 4;
> +               put_unaligned((u32)((r << 20) | (g << 10) | b), (u32 *)dst);
> +               break;
> +       }
> +}
> +
> +static void sdrm_blit_from_xrgb8888(const u8 *src, u32 src_stride, u32 src_bpp,
> +                                   u8 *dst, u32 dst_stride, u32 dst_bpp,
> +                                   u32 dst_four_cc, u32 width, u32 height)
> +{
> +       u32 val, i;
> +
> +       while (height--) {
> +               for (i = 0; i < width; ++i) {
> +                       val = get_unaligned((const u32 *)&src[i * src_bpp]);
> +                       sdrm_put(&dst[i * dst_bpp], dst_four_cc,
> +                                (val & 0x00ff0000U) >> 8,
> +                                (val & 0x0000ff00U),
> +                                (val & 0x000000ffU) << 8);
> +               }
> +
> +               src += src_stride;
> +               dst += dst_stride;
> +       }
> +}
> +
> +static void sdrm_blit_from_rgb565(const u8 *src, u32 src_stride, u32 src_bpp,
> +                                 u8 *dst, u32 dst_stride, u32 dst_bpp,
> +                                 u32 dst_four_cc, u32 width, u32 height)
> +{
> +       u32 val, i;
> +
> +       while (height--) {
> +               for (i = 0; i < width; ++i) {
> +                       val = get_unaligned((const u16 *)&src[i * src_bpp]);
> +                       sdrm_put(&dst[i * dst_bpp], dst_four_cc,
> +                                (val & 0xf800),
> +                                (val & 0x07e0) << 5,
> +                                (val & 0x001f) << 11);
> +               }
> +
> +               src += src_stride;
> +               dst += dst_stride;
> +       }
> +}
> +
> +static void sdrm_blit_lines(const u8 *src, u32 src_stride,
> +                           u8 *dst, u32 dst_stride,
> +                           u32 bpp, u32 width, u32 height)
> +{
> +       u32 len;
> +
> +       len = width * bpp;
> +
> +       while (height--) {
> +               memcpy(dst, src, len);
> +               src += src_stride;
> +               dst += dst_stride;
> +       }
> +}
> +
> +static void sdrm_blit(struct sdrm_framebuffer *sfb, u32 x, u32 y,
> +                     u32 width, u32 height)
> +{
> +       struct drm_framebuffer *fb = &sfb->base;
> +       struct drm_device *ddev = fb->dev;
> +       struct sdrm_device *sdrm = ddev->dev_private;
> +       u32 src_bpp, dst_bpp;
> +       u8 *src, *dst;
> +
> +       /* already unmapped; ongoing handover? */
> +       if (!sdrm->fb_map)
> +               return;
> +
> +       /* get buffer offsets */
> +       src = sfb->obj->vmapping;
> +       dst = sdrm->fb_map;
> +
> +       /* bo is guaranteed to be big enough; size checks not needed */
> +       src_bpp = (fb->bits_per_pixel + 7) / 8;
> +       src += fb->offsets[0] + y * fb->pitches[0] + x * src_bpp;
> +
> +       dst_bpp = (sdrm->fb_bpp + 7) / 8;
> +       dst += y * sdrm->fb_stride + x * dst_bpp;
> +
> +       /* if formats are identical, do a line-by-line copy.. */
> +       if (fb->pixel_format == sdrm->fb_format) {
> +               sdrm_blit_lines(src, fb->pitches[0],
> +                               dst, sdrm->fb_stride,
> +                               src_bpp, width, height);
> +               return;
> +       }
> +
> +       /* ..otherwise call slow blit-function */
> +       switch (fb->pixel_format) {
> +       case DRM_FORMAT_ARGB8888:
> +               /* fallthrough */
> +       case DRM_FORMAT_XRGB8888:
> +               sdrm_blit_from_xrgb8888(src, fb->pitches[0], src_bpp,
> +                                       dst, sdrm->fb_stride, dst_bpp,
> +                                       sdrm->fb_format, width, height);
> +               break;
> +       case DRM_FORMAT_RGB565:
> +               sdrm_blit_from_rgb565(src, fb->pitches[0], src_bpp,
> +                                     dst, sdrm->fb_stride, dst_bpp,
> +                                     sdrm->fb_format, width, height);
> +               break;
> +       }
> +}
> +
> +int sdrm_dirty(struct drm_framebuffer *fb,
> +              struct drm_file *file,
> +              unsigned int flags, unsigned int color,
> +              struct drm_clip_rect *clips,
> +              unsigned int num_clips)
> +{
> +       struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
> +       struct drm_device *ddev = fb->dev;
> +       struct sdrm_device *sdrm = ddev->dev_private;
> +       struct drm_clip_rect full_clip;
> +       unsigned int i;
> +       int r;
> +
> +       if (!clips || !num_clips) {
> +               full_clip.x1 = 0;
> +               full_clip.x2 = fb->width;
> +               full_clip.y1 = 0;
> +               full_clip.y2 = fb->height;
> +               clips = &full_clip;
> +               num_clips = 1;
> +       }
> +
> +       drm_modeset_lock_all(ddev);
> +
> +       if (sdrm->pipe.plane.fb != fb) {
> +               r = 0;
> +               goto unlock;
> +       }
> +
> +       for (i = 0; i < num_clips; i++) {
> +               if (clips[i].x1 > clips[i].x2 || clips[i].x2 > fb->width ||
> +                   clips[i].y1 > clips[i].y2 || clips[i].y2 > fb->height)
> +                       continue;
> +
> +               sdrm_blit(sfb, clips[i].x1, clips[i].y1,
> +                         clips[i].x2 - clips[i].x1,
> +                         clips[i].y2 - clips[i].y1);
> +       }
> +
> +unlock:
> +       drm_modeset_unlock_all(ddev);
> +       return 0;
> +}
> +
> +int sdrm_dirty_all_locked(struct sdrm_device *sdrm)
> +{
> +       struct drm_framebuffer *fb;
> +       struct sdrm_framebuffer *sfb;
> +
> +       fb = sdrm->pipe.plane.fb;
> +       if (!fb)
> +               return 0;
> +
> +       sfb = to_sdrm_fb(fb);
> +
> +       sdrm_blit(sfb, 0, 0, fb->width, fb->height);
> +
> +       return 0;
> +}
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> new file mode 100644
> index 0000000..17c1b55
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> @@ -0,0 +1,543 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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 <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/errno.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/simplefb.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/string.h>
> +
> +#include "simpledrm.h"
> +
> +static const struct file_operations sdrm_drm_fops = {
> +       .owner = THIS_MODULE,
> +       .open = drm_open,
> +       .mmap = sdrm_drm_mmap,
> +       .poll = drm_poll,
> +       .read = drm_read,
> +       .unlocked_ioctl = drm_ioctl,
> +       .release = drm_release,
> +#ifdef CONFIG_COMPAT
> +       .compat_ioctl = drm_compat_ioctl,
> +#endif
> +       .llseek = noop_llseek,
> +};
> +
> +static const struct vm_operations_struct sdrm_gem_vm_ops = {
> +       .fault = sdrm_gem_fault,
> +       .open = drm_gem_vm_open,
> +       .close = drm_gem_vm_close,
> +};
> +
> +static struct drm_driver sdrm_drm_driver = {
> +       .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
> +       .fops = &sdrm_drm_fops,
> +
> +       .gem_free_object = sdrm_gem_free_object,
> +       .gem_vm_ops = &sdrm_gem_vm_ops,
> +
> +       .dumb_create = sdrm_dumb_create,
> +       .dumb_map_offset = sdrm_dumb_map_offset,
> +       .dumb_destroy = drm_gem_dumb_destroy,
> +
> +       .name = "simpledrm",
> +       .desc = "Simple firmware framebuffer DRM driver",
> +       .date = "20130601",
> +       .major = 0,
> +       .minor = 0,
> +       .patchlevel = 1,
> +};
> +
> +#if defined CONFIG_OF && defined CONFIG_COMMON_CLK
> +/*
> + * Clock handling code.
> + *
> + * Here we handle the clocks property of our "simple-framebuffer" dt node.
> + * This is necessary so that we can make sure that any clocks needed by
> + * the display engine that the bootloader set up for us (and for which it
> + * provided a simplefb dt node), stay up, for the life of the simplefb
> + * driver.
> + *
> + * When the driver unloads, we cleanly disable, and then release the clocks.
> + *
> + * We only complain about errors here, no action is taken as the most likely
> + * error can only happen due to a mismatch between the bootloader which set
> + * up simplefb, and the clock definitions in the device tree. Chances are
> + * that there are no adverse effects, and if there are, a clean teardown of
> + * the fb probe will not help us much either. So just complain and carry on,
> + * and hope that the user actually gets a working fb at the end of things.
> + */
> +static int sdrm_clocks_init(struct sdrm_device *sdrm,
> +                           struct platform_device *pdev)
> +{
> +       struct device_node *np = pdev->dev.of_node;
> +       struct clk *clock;
> +       int i, ret;
> +
> +       if (dev_get_platdata(&pdev->dev) || !np)
> +               return 0;
> +
> +       sdrm->clk_count = of_clk_get_parent_count(np);
> +       if (!sdrm->clk_count)
> +               return 0;
> +
> +       sdrm->clks = kcalloc(sdrm->clk_count, sizeof(struct clk *), GFP_KERNEL);
> +       if (!sdrm->clks)
> +               return -ENOMEM;
> +
> +       for (i = 0; i < sdrm->clk_count; i++) {
> +               clock = of_clk_get(np, i);
> +               if (IS_ERR(clock)) {
> +                       if (PTR_ERR(clock) == -EPROBE_DEFER) {
> +                               while (--i >= 0) {
> +                                       if (sdrm->clks[i])
> +                                               clk_put(sdrm->clks[i]);
> +                               }
> +                               kfree(sdrm->clks);
> +                               return -EPROBE_DEFER;
> +                       }
> +                       dev_err(&pdev->dev, "%s: clock %d not found: %ld\n",
> +                               __func__, i, PTR_ERR(clock));
> +                       continue;
> +               }
> +               sdrm->clks[i] = clock;
> +       }
> +
> +       for (i = 0; i < sdrm->clk_count; i++) {
> +               if (sdrm->clks[i]) {
> +                       ret = clk_prepare_enable(sdrm->clks[i]);
> +                       if (ret) {
> +                               dev_err(&pdev->dev,
> +                                       "%s: failed to enable clock %d: %d\n",
> +                                       __func__, i, ret);
> +                               clk_put(sdrm->clks[i]);
> +                               sdrm->clks[i] = NULL;
> +                       }
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static void sdrm_clocks_destroy(struct sdrm_device *sdrm)
> +{
> +       int i;
> +
> +       if (!sdrm->clks)
> +               return;
> +
> +       for (i = 0; i < sdrm->clk_count; i++) {
> +               if (sdrm->clks[i]) {
> +                       clk_disable_unprepare(sdrm->clks[i]);
> +                       clk_put(sdrm->clks[i]);
> +               }
> +       }
> +
> +       kfree(sdrm->clks);
> +}
> +#else
> +static int sdrm_clocks_init(struct sdrm_device *sdrm,
> +                           struct platform_device *pdev)
> +{
> +       return 0;
> +}
> +
> +static void sdrm_clocks_destroy(struct sdrm_device *sdrm)
> +{
> +}
> +#endif
> +
> +#if defined CONFIG_OF && defined CONFIG_REGULATOR
> +
> +#define SUPPLY_SUFFIX "-supply"
> +
> +/*
> + * Regulator handling code.
> + *
> + * Here we handle the num-supplies and vin*-supply properties of our
> + * "simple-framebuffer" dt node. This is necessary so that we can make sure
> + * that any regulators needed by the display hardware that the bootloader
> + * set up for us (and for which it provided a simplefb dt node), stay up,
> + * for the life of the simplefb driver.
> + *
> + * When the driver unloads, we cleanly disable, and then release the
> + * regulators.
> + *
> + * We only complain about errors here, no action is taken as the most likely
> + * error can only happen due to a mismatch between the bootloader which set
> + * up simplefb, and the regulator definitions in the device tree. Chances are
> + * that there are no adverse effects, and if there are, a clean teardown of
> + * the fb probe will not help us much either. So just complain and carry on,
> + * and hope that the user actually gets a working fb at the end of things.
> + */
> +static int sdrm_regulators_init(struct sdrm_device *sdrm,
> +                               struct platform_device *pdev)
> +{
> +       struct device_node *np = pdev->dev.of_node;
> +       struct regulator *regulator;
> +       int count = 0, i = 0, ret;
> +       struct property *prop;
> +       const char *p;
> +
> +       if (dev_get_platdata(&pdev->dev) || !np)
> +               return 0;
> +
> +       /* Count the number of regulator supplies */
> +       for_each_property_of_node(np, prop) {
> +               p = strstr(prop->name, SUPPLY_SUFFIX);
> +               if (p && p != prop->name)
> +                       count++;
> +       }
> +
> +       if (!count)
> +               return 0;
> +
> +       sdrm->regulators = devm_kcalloc(&pdev->dev, count,
> +                                       sizeof(struct regulator *),
> +                                       GFP_KERNEL);
> +       if (!sdrm->regulators)
> +               return -ENOMEM;
> +
> +       /* Get all the regulators */
> +       for_each_property_of_node(np, prop) {
> +               char name[32]; /* 32 is max size of property name */
> +
> +               p = strstr(prop->name, SUPPLY_SUFFIX);
> +               if (!p || p == prop->name)
> +                       continue;
> +
> +               strlcpy(name, prop->name,
> +                       strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1);
> +               regulator = devm_regulator_get_optional(&pdev->dev, name);
> +               if (IS_ERR(regulator)) {
> +                       if (PTR_ERR(regulator) == -EPROBE_DEFER)
> +                               return -EPROBE_DEFER;
> +                       dev_err(&pdev->dev, "regulator %s not found: %ld\n",
> +                               name, PTR_ERR(regulator));
> +                       continue;
> +               }
> +               sdrm->regulators[i++] = regulator;
> +       }
> +       sdrm->regulator_count = i;
> +
> +       /* Enable all the regulators */
> +       for (i = 0; i < sdrm->regulator_count; i++) {
> +               ret = regulator_enable(sdrm->regulators[i]);
> +               if (ret) {
> +                       dev_err(&pdev->dev,
> +                               "failed to enable regulator %d: %d\n",
> +                               i, ret);
> +                       devm_regulator_put(sdrm->regulators[i]);
> +                       sdrm->regulators[i] = NULL;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static void sdrm_regulators_destroy(struct sdrm_device *sdrm)
> +{
> +       int i;
> +
> +       if (!sdrm->regulators)
> +               return;
> +
> +       for (i = 0; i < sdrm->regulator_count; i++)
> +               if (sdrm->regulators[i])
> +                       regulator_disable(sdrm->regulators[i]);
> +}
> +#else
> +static int sdrm_regulators_init(struct sdrm_device *sdrm,
> +                               struct platform_device *pdev)
> +{
> +       return 0;
> +}
> +
> +static void sdrm_regulators_destroy(struct sdrm_device *sdrm)
> +{
> +}
> +#endif
> +
> +static int parse_dt(struct platform_device *pdev,
> +                   struct simplefb_platform_data *mode)
> +{
> +       struct device_node *np = pdev->dev.of_node;
> +       const char *format;
> +       int ret;
> +
> +       if (!np)
> +               return -ENODEV;
> +
> +       ret = of_property_read_u32(np, "width", &mode->width);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Can't parse width property\n");
> +               return ret;
> +       }
> +
> +       ret = of_property_read_u32(np, "height", &mode->height);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Can't parse height property\n");
> +               return ret;
> +       }
> +
> +       ret = of_property_read_u32(np, "stride", &mode->stride);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Can't parse stride property\n");
> +               return ret;
> +       }
> +
> +       ret = of_property_read_string(np, "format", &format);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Can't parse format property\n");
> +               return ret;
> +       }
> +       mode->format = format;
> +
> +       return 0;
> +}
> +
> +static struct simplefb_format simplefb_formats[] = SIMPLEFB_FORMATS;
> +
> +static int sdrm_pdev_init(struct sdrm_device *sdrm,
> +                         struct platform_device *pdev)
> +{
> +       struct simplefb_platform_data *mode = pdev->dev.platform_data;
> +       struct simplefb_platform_data pmode;
> +       struct resource *mem;
> +       unsigned int depth;
> +       int ret, i, bpp;
> +
> +       if (!mode) {
> +               mode = &pmode;
> +               ret = parse_dt(pdev, mode);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (!mem) {
> +               dev_err(sdrm->ddev->dev, "No memory resource\n");
> +               return -ENODEV;
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(simplefb_formats); ++i) {
> +               if (strcmp(mode->format, simplefb_formats[i].name))
> +                       continue;
> +
> +               sdrm->fb_sformat = &simplefb_formats[i];
> +               sdrm->fb_format = simplefb_formats[i].fourcc;
> +               sdrm->fb_width = mode->width;
> +               sdrm->fb_height = mode->height;
> +               sdrm->fb_stride = mode->stride;
> +               sdrm->fb_base = mem->start;
> +               sdrm->fb_size = resource_size(mem);
> +               break;
> +       }
> +
> +       if (i >= ARRAY_SIZE(simplefb_formats)) {
> +               dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
> +               return -ENODEV;
> +       }
> +
> +       switch (sdrm->fb_format) {
> +       case DRM_FORMAT_RGB565:
> +       case DRM_FORMAT_XRGB1555:
> +       case DRM_FORMAT_ARGB1555:
> +       case DRM_FORMAT_RGB888:
> +       case DRM_FORMAT_XRGB8888:
> +       case DRM_FORMAT_ARGB8888:
> +       case DRM_FORMAT_ABGR8888:
> +       case DRM_FORMAT_XRGB2101010:
> +       case DRM_FORMAT_ARGB2101010:
> +               /*
> +                * You must adjust sdrm_put() whenever you add a new format
> +                * here, otherwise, blitting operations will not work.
> +                * Furthermore, include/linux/platform_data/simplefb.h needs
> +                * to be adjusted so the platform-device actually allows this
> +                * format.
> +                */
> +               break;
> +       default:
> +               dev_err(sdrm->ddev->dev, "Unsupported format %s\n",
> +                       mode->format);
> +               return -ENODEV;
> +       }
> +
> +       drm_fb_get_bpp_depth(sdrm->fb_format, &depth, &bpp);
> +       if (!bpp) {
> +               dev_err(sdrm->ddev->dev, "Unknown format %s\n", mode->format);
> +               return -ENODEV;
> +       }
> +
> +       if (sdrm->fb_size < sdrm->fb_stride * sdrm->fb_height) {
> +               dev_err(sdrm->ddev->dev, "FB too small\n");
> +               return -ENODEV;
> +       } else if ((bpp + 7) / 8 * sdrm->fb_width > sdrm->fb_stride) {
> +               dev_err(sdrm->ddev->dev, "Invalid stride\n");
> +               return -ENODEV;
> +       }
> +
> +       sdrm->fb_bpp = bpp;
> +
> +       sdrm->fb_map = ioremap_wc(sdrm->fb_base, sdrm->fb_size);
> +       if (!sdrm->fb_map) {
> +               dev_err(sdrm->ddev->dev, "cannot remap VMEM\n");
> +               return -EIO;
> +       }
> +
> +       DRM_DEBUG_KMS("format: %s\n", drm_get_format_name(sdrm->fb_format));
> +
> +       return 0;
> +}
> +
> +static void sdrm_pdev_destroy(struct sdrm_device *sdrm)
> +{
> +       if (sdrm->fb_map) {
> +               iounmap(sdrm->fb_map);
> +               sdrm->fb_map = NULL;
> +       }
> +}
> +
> +static int sdrm_simplefb_probe(struct platform_device *pdev)
> +{
> +       struct sdrm_device *sdrm;
> +       struct drm_device *ddev;
> +       int ret;
> +
> +       ddev = drm_dev_alloc(&sdrm_drm_driver, &pdev->dev);
> +       if (!ddev)
> +               return -ENOMEM;
> +
> +       sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
> +       if (!sdrm)
> +               goto err_free;
> +
> +       ddev->dev_private = sdrm;
> +       sdrm->ddev = ddev;
> +
> +       ret = sdrm_pdev_init(sdrm, pdev);
> +       if (ret)
> +               goto err_free;
> +
> +       ret = sdrm_drm_modeset_init(sdrm);
> +       if (ret)
> +               goto err_destroy;
> +
> +       ret = sdrm_clocks_init(sdrm, pdev);
> +       if (ret < 0)
> +               goto err_cleanup;
> +
> +       ret = sdrm_regulators_init(sdrm, pdev);
> +       if (ret < 0)
> +               goto err_clocks;
> +
> +       platform_set_drvdata(pdev, ddev);
> +       ret = drm_dev_register(ddev, 0);
> +       if (ret)
> +               goto err_regulators;
> +
> +       DRM_INFO("Initialized %s on minor %d\n", ddev->driver->name,
> +                ddev->primary->index);
> +
> +       return 0;
> +
> +err_regulators:
> +       sdrm_regulators_destroy(sdrm);
> +err_clocks:
> +       sdrm_clocks_destroy(sdrm);
> +err_cleanup:
> +       drm_mode_config_cleanup(ddev);
> +err_destroy:
> +       sdrm_pdev_destroy(sdrm);
> +err_free:
> +       drm_dev_unref(ddev);
> +       kfree(sdrm);
> +
> +       return ret;
> +}
> +
> +static int sdrm_simplefb_remove(struct platform_device *pdev)
> +{
> +       struct drm_device *ddev = platform_get_drvdata(pdev);
> +       struct sdrm_device *sdrm = ddev->dev_private;
> +
> +       drm_dev_unregister(ddev);
> +       drm_mode_config_cleanup(ddev);
> +
> +       /* protect fb_map removal against sdrm_blit() */
> +       drm_modeset_lock_all(ddev);
> +       sdrm_pdev_destroy(sdrm);
> +       drm_modeset_unlock_all(ddev);
> +
> +       sdrm_regulators_destroy(sdrm);
> +       sdrm_clocks_destroy(sdrm);
> +
> +       drm_dev_unref(ddev);
> +       kfree(sdrm);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id simplefb_of_match[] = {
> +       { .compatible = "simple-framebuffer", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, simplefb_of_match);
> +
> +static struct platform_driver sdrm_simplefb_driver = {
> +       .probe = sdrm_simplefb_probe,
> +       .remove = sdrm_simplefb_remove,
> +       .driver = {
> +               .name = "simple-framebuffer",
> +               .mod_name = KBUILD_MODNAME,
> +               .owner = THIS_MODULE,
> +               .of_match_table = simplefb_of_match,
> +       },
> +};
> +
> +static int __init sdrm_init(void)
> +{
> +       int ret;
> +
> +       ret = platform_driver_register(&sdrm_simplefb_driver);
> +       if (ret)
> +               return ret;
> +
> +       if (IS_ENABLED(CONFIG_OF_ADDRESS) && of_chosen) {
> +               struct device_node *np;
> +
> +               for_each_child_of_node(of_chosen, np) {
> +                       if (of_device_is_compatible(np, "simple-framebuffer"))
> +                               of_platform_device_create(np, NULL, NULL);
> +               }
> +       }
> +
> +       return 0;
> +}
> +module_init(sdrm_init);
> +
> +static void __exit sdrm_exit(void)
> +{
> +       platform_driver_unregister(&sdrm_simplefb_driver);
> +}
> +module_exit(sdrm_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
> +MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
> +MODULE_ALIAS("platform:simple-framebuffer");
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
> new file mode 100644
> index 0000000..8cced80
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c
> @@ -0,0 +1,202 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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 <linux/errno.h>
> +#include <linux/mm.h>
> +#include <linux/slab.h>
> +#include <linux/vmalloc.h>
> +
> +#include "simpledrm.h"
> +
> +struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev,
> +                                             size_t size)
> +{
> +       struct sdrm_gem_object *obj;
> +
> +       WARN_ON(!size || (size & ~PAGE_MASK) != 0);
> +
> +       obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> +       if (!obj)
> +               return NULL;
> +
> +       if (drm_gem_object_init(ddev, &obj->base, size)) {
> +               kfree(obj);
> +               return NULL;
> +       }
> +
> +       return obj;
> +}
> +
> +int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
> +                    struct drm_mode_create_dumb *args)
> +{
> +       struct sdrm_gem_object *obj;
> +       int r;
> +
> +       /* overflow checks are done by DRM core */
> +       args->pitch = (args->bpp + 7) / 8 * args->width;
> +       args->size = PAGE_ALIGN(args->pitch * args->height);
> +
> +       obj = sdrm_gem_alloc_object(ddev, args->size);
> +       if (!obj)
> +               return -ENOMEM;
> +
> +       r = drm_gem_handle_create(dfile, &obj->base, &args->handle);
> +       if (r) {
> +               drm_gem_object_unreference_unlocked(&obj->base);
> +               return r;
> +       }
> +
> +       /* handle owns a reference */
> +       drm_gem_object_unreference_unlocked(&obj->base);
> +
> +       return 0;
> +}
> +
> +int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> +       int ret;
> +
> +       ret = drm_gem_mmap(filp, vma);
> +       if (ret)
> +               return ret;
> +
> +       vma->vm_flags &= ~VM_PFNMAP;
> +       vma->vm_flags |= VM_MIXEDMAP;
> +       vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> +
> +       return 0;
> +}
> +
> +int sdrm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
> +{
> +       struct drm_gem_object *gobj = vma->vm_private_data;
> +       struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
> +       pgoff_t offset;
> +       int ret;
> +
> +       if (!obj->pages)
> +               return VM_FAULT_SIGBUS;
> +
> +       offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >>
> +                PAGE_SHIFT;
> +
> +       ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address,
> +                            obj->pages[offset]);
> +       switch (ret) {
> +       case -EAGAIN:
> +       case 0:
> +       case -ERESTARTSYS:
> +       case -EINTR:
> +       case -EBUSY:
> +               return VM_FAULT_NOPAGE;
> +
> +       case -ENOMEM:
> +               return VM_FAULT_OOM;
> +       }
> +
> +       return VM_FAULT_SIGBUS;
> +}
> +
> +int sdrm_gem_get_pages(struct sdrm_gem_object *obj)
> +{
> +       struct page **pages;
> +
> +       if (obj->pages)
> +               return 0;
> +
> +       pages = drm_gem_get_pages(&obj->base);
> +       if (IS_ERR(pages))
> +               return PTR_ERR(pages);
> +
> +       obj->pages = pages;
> +
> +       return 0;
> +}
> +
> +static void sdrm_gem_put_pages(struct sdrm_gem_object *obj)
> +{
> +       if (!obj->pages)
> +               return;
> +
> +       drm_gem_put_pages(&obj->base, obj->pages, false, false);
> +       obj->pages = NULL;
> +}
> +
> +int sdrm_gem_vmap(struct sdrm_gem_object *obj)
> +{
> +       int page_count = obj->base.size / PAGE_SIZE;
> +       int ret;
> +
> +       if (obj->vmapping)
> +               return 0;
> +
> +       ret = sdrm_gem_get_pages(obj);
> +       if (ret)
> +               return ret;
> +
> +       obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
> +       if (!obj->vmapping)
> +               return -ENOMEM;
> +
> +       return 0;
> +}
> +
> +static void sdrm_gem_vunmap(struct sdrm_gem_object *obj)
> +{
> +       vunmap(obj->vmapping);
> +       obj->vmapping = NULL;
> +
> +       sdrm_gem_put_pages(obj);
> +}
> +
> +void sdrm_gem_free_object(struct drm_gem_object *gobj)
> +{
> +       struct sdrm_gem_object *obj = to_sdrm_bo(gobj);
> +
> +       if (obj->vmapping)
> +               sdrm_gem_vunmap(obj);
> +
> +       if (obj->pages)
> +               sdrm_gem_put_pages(obj);
> +
> +       drm_gem_object_release(gobj);
> +       kfree(obj);
> +}
> +
> +int sdrm_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
> +                        uint32_t handle, uint64_t *offset)
> +{
> +       struct sdrm_gem_object *obj;
> +       struct drm_gem_object *gobj;
> +       int ret;
> +
> +       gobj = drm_gem_object_lookup(dfile, handle);
> +       if (!gobj)
> +               return -ENOENT;
> +
> +       obj = to_sdrm_bo(gobj);
> +
> +       ret = sdrm_gem_get_pages(obj);
> +       if (ret)
> +               goto out_unref;
> +
> +       ret = drm_gem_create_mmap_offset(gobj);
> +       if (ret)
> +               goto out_unref;
> +
> +       *offset = drm_vma_node_offset_addr(&gobj->vma_node);
> +
> +out_unref:
> +       drm_gem_object_unreference_unlocked(gobj);
> +
> +       return ret;
> +}
> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
> new file mode 100644
> index 0000000..e6dc3df
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c
> @@ -0,0 +1,234 @@
> +/*
> + * SimpleDRM firmware framebuffer driver
> + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@gmail.com>
> + *
> + * 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_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_gem.h>
> +#include <drm/drm_simple_kms_helper.h>
> +#include <linux/slab.h>
> +
> +#include "simpledrm.h"
> +
> +static const uint32_t sdrm_formats[] = {
> +       DRM_FORMAT_RGB565,
> +       DRM_FORMAT_ARGB8888,
> +       DRM_FORMAT_XRGB8888,
> +};
> +
> +static int sdrm_conn_get_modes(struct drm_connector *conn)
> +{
> +       struct sdrm_device *sdrm = conn->dev->dev_private;
> +       struct drm_display_mode *mode;
> +
> +       mode = drm_cvt_mode(sdrm->ddev, sdrm->fb_width, sdrm->fb_height,
> +                           60, false, false, false);
> +       if (!mode)
> +               return 0;
> +
> +       mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> +       drm_mode_set_name(mode);
> +       drm_mode_probed_add(conn, mode);
> +
> +       return 1;
> +}
> +
> +static const struct drm_connector_helper_funcs sdrm_conn_hfuncs = {
> +       .get_modes = sdrm_conn_get_modes,
> +       .best_encoder = drm_atomic_helper_best_encoder,
> +};
> +
> +static enum drm_connector_status sdrm_conn_detect(struct drm_connector *conn,
> +                                                 bool force)
> +{
> +       /*
> +        * We simulate an always connected monitor. simple-fb doesn't
> +        * provide any way to detect whether the connector is active. Hence,
> +        * signal DRM core that it is always connected.
> +        */
> +
> +       return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs sdrm_conn_ops = {
> +       .dpms = drm_atomic_helper_connector_dpms,
> +       .reset = drm_atomic_helper_connector_reset,
> +       .detect = sdrm_conn_detect,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .destroy = drm_connector_cleanup,
> +       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static inline struct sdrm_device *
> +pipe_to_sdrm(struct drm_simple_display_pipe *pipe)
> +{
> +       return container_of(pipe, struct sdrm_device, pipe);
> +}
> +
> +static void sdrm_crtc_send_vblank_event(struct drm_crtc *crtc)
> +{
> +       if (crtc->state && crtc->state->event) {
> +               spin_lock_irq(&crtc->dev->event_lock);
> +               drm_crtc_send_vblank_event(crtc, crtc->state->event);
> +               spin_unlock_irq(&crtc->dev->event_lock);
> +               crtc->state->event = NULL;
> +       }

So either the drm_simple_kms_helpers are buggy, or the SimpleDRM use
of it. On DPMS updates, I get:

Sep 02 01:00:39 david-t2 kernel:
[drm:drm_atomic_helper_commit_cleanup_done [drm_kms_helper]] *ERROR*
[CRTC:25:crtc-0] flip_done timed out
Sep 02 01:00:39 david-t2 kernel: ------------[ cut here ]------------
Sep 02 01:00:39 david-t2 kernel: WARNING: CPU: 3 PID: 352 at
drivers/gpu/drm/drm_atomic_helper.c:1549
drm_atomic_helper_commit_hw_done+0xab/0xb0 [drm_kms_helper]

The atomic-commit originates in:
drm_atomic_helper_connector_dpms()

Any idea what is missing there? I haven't looked much into the
atomic-helpers, yet.

Thanks
David

> +}
> +
> +void sdrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
> +                             struct drm_plane_state *plane_state)
> +{
> +       struct drm_framebuffer *fb = pipe->plane.state->fb;
> +       struct sdrm_device *sdrm = pipe_to_sdrm(pipe);
> +
> +       sdrm_crtc_send_vblank_event(&pipe->crtc);
> +
> +       if (fb) {
> +               pipe->plane.fb = fb;
> +               sdrm_dirty_all_locked(sdrm);
> +       }
> +}
> +
> +static void sdrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
> +                                    struct drm_crtc_state *crtc_state)
> +{
> +       sdrm_crtc_send_vblank_event(&pipe->crtc);
> +}
> +
> +static void sdrm_display_pipe_disable(struct drm_simple_display_pipe *pipe)
> +{
> +       sdrm_crtc_send_vblank_event(&pipe->crtc);
> +}
> +
> +static const struct drm_simple_display_pipe_funcs sdrm_pipe_funcs = {
> +       .update = sdrm_display_pipe_update,
> +       .enable = sdrm_display_pipe_enable,
> +       .disable = sdrm_display_pipe_disable,
> +};
> +
> +static int sdrm_fb_create_handle(struct drm_framebuffer *fb,
> +                                struct drm_file *dfile,
> +                                unsigned int *handle)
> +{
> +       struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
> +
> +       return drm_gem_handle_create(dfile, &sfb->obj->base, handle);
> +}
> +
> +static void sdrm_fb_destroy(struct drm_framebuffer *fb)
> +{
> +       struct sdrm_framebuffer *sfb = to_sdrm_fb(fb);
> +
> +       drm_framebuffer_cleanup(fb);
> +       drm_gem_object_unreference_unlocked(&sfb->obj->base);
> +       kfree(sfb);
> +}
> +
> +static const struct drm_framebuffer_funcs sdrm_fb_ops = {
> +       .create_handle = sdrm_fb_create_handle,
> +       .dirty = sdrm_dirty,
> +       .destroy = sdrm_fb_destroy,
> +};
> +
> +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)
> +{
> +       fb->obj = obj;
> +       drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd);
> +
> +       return drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops);
> +}
> +
> +static struct drm_framebuffer *sdrm_fb_create(struct drm_device *ddev,
> +                                             struct drm_file *dfile,
> +                                             const struct drm_mode_fb_cmd2 *cmd)
> +{
> +       struct sdrm_framebuffer *fb;
> +       struct drm_gem_object *gobj;
> +       int ret;
> +
> +       if (cmd->flags)
> +               return ERR_PTR(-EINVAL);
> +
> +       gobj = drm_gem_object_lookup(dfile, cmd->handles[0]);
> +       if (!gobj)
> +               return ERR_PTR(-EINVAL);
> +
> +       fb = kzalloc(sizeof(*fb), GFP_KERNEL);
> +       if (!fb) {
> +               ret = -ENOMEM;
> +               goto err_unref;
> +       }
> +
> +       ret = sdrm_fb_init(ddev, fb, cmd, to_sdrm_bo(gobj));
> +       if (ret)
> +               goto err_free;
> +
> +       ret = sdrm_gem_vmap(fb->obj);
> +       if (ret)
> +               goto err_free;
> +
> +       DRM_DEBUG_KMS("[FB:%d] pixel_format: %s\n", fb->base.base.id,
> +                     drm_get_format_name(fb->base.pixel_format));
> +
> +       return &fb->base;
> +
> +err_free:
> +       kfree(fb);
> +err_unref:
> +       drm_gem_object_unreference_unlocked(gobj);
> +
> +       return ERR_PTR(ret);
> +}
> +
> +static const struct drm_mode_config_funcs sdrm_mode_config_ops = {
> +       .fb_create = sdrm_fb_create,
> +       .atomic_check = drm_atomic_helper_check,
> +       .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +int sdrm_drm_modeset_init(struct sdrm_device *sdrm)
> +{
> +       struct drm_connector *conn = &sdrm->conn;
> +       struct drm_device *ddev = sdrm->ddev;
> +       int ret;
> +
> +       drm_mode_config_init(ddev);
> +       ddev->mode_config.min_width = sdrm->fb_width;
> +       ddev->mode_config.max_width = sdrm->fb_width;
> +       ddev->mode_config.min_height = sdrm->fb_height;
> +       ddev->mode_config.max_height = sdrm->fb_height;
> +       ddev->mode_config.preferred_depth = sdrm->fb_bpp;
> +       ddev->mode_config.funcs = &sdrm_mode_config_ops;
> +
> +       drm_connector_helper_add(conn, &sdrm_conn_hfuncs);
> +       ret = drm_connector_init(ddev, conn, &sdrm_conn_ops,
> +                                DRM_MODE_CONNECTOR_VIRTUAL);
> +       if (ret)
> +               goto err_cleanup;
> +
> +       ret = drm_simple_display_pipe_init(ddev, &sdrm->pipe, &sdrm_pipe_funcs,
> +                                          sdrm_formats,
> +                                          ARRAY_SIZE(sdrm_formats), conn);
> +       if (ret)
> +               goto err_cleanup;
> +
> +       drm_mode_config_reset(ddev);
> +
> +       return 0;
> +
> +err_cleanup:
> +       drm_mode_config_cleanup(ddev);
> +
> +       return ret;
> +}
> --
> 2.8.2
>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4 3/5] drm: add SimpleDRM driver
  2016-09-01 23:48   ` David Herrmann
@ 2016-09-02  6:11     ` Daniel Vetter
  0 siblings, 0 replies; 19+ messages in thread
From: Daniel Vetter @ 2016-09-02  6:11 UTC (permalink / raw)
  To: David Herrmann; +Cc: Noralf Trønnes, linux-kernel, dri-devel, devicetree

On Fri, Sep 2, 2016 at 1:48 AM, David Herrmann <dh.herrmann@gmail.com> wrote:
> So either the drm_simple_kms_helpers are buggy, or the SimpleDRM use
> of it. On DPMS updates, I get:
>
> Sep 02 01:00:39 david-t2 kernel:
> [drm:drm_atomic_helper_commit_cleanup_done [drm_kms_helper]] *ERROR*
> [CRTC:25:crtc-0] flip_done timed out
> Sep 02 01:00:39 david-t2 kernel: ------------[ cut here ]------------
> Sep 02 01:00:39 david-t2 kernel: WARNING: CPU: 3 PID: 352 at
> drivers/gpu/drm/drm_atomic_helper.c:1549
> drm_atomic_helper_commit_hw_done+0xab/0xb0 [drm_kms_helper]
>
> The atomic-commit originates in:
> drm_atomic_helper_connector_dpms()
>
> Any idea what is missing there? I haven't looked much into the
> atomic-helpers, yet.

The bugfix for that is in latest drm-misc (or well, should be at
least). And you need to make sure to call drm_crtc_send_vblank_event
(if there's an event) from your update hook. That /should/ work, but I
didn't test it myself. flip_done not happening is when the
driver/helpers fail to submit the event for some reason or another.
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2016-09-02  6:11 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-08-22 20:25 [PATCH v4 0/5] drm: add SimpleDRM driver Noralf Trønnes
2016-08-22 20:25 ` [PATCH v4 1/5] of: Add EXPORT_SYMBOL for of_chosen Noralf Trønnes
2016-08-22 20:25 ` [PATCH v4 2/5] drm/fb-helper: Add drm_fb_helper_set_suspend_lock() Noralf Trønnes
2016-08-23  6:07   ` Daniel Vetter
2016-08-22 20:25 ` [PATCH v4 3/5] drm: add SimpleDRM driver Noralf Trønnes
2016-08-23  6:17   ` Daniel Vetter
2016-08-25 22:11     ` Noralf Trønnes
2016-08-26 14:40       ` Daniel Vetter
2016-08-25 13:09   ` Rob Herring
2016-08-25 18:42     ` Noralf Trønnes
2016-09-01 23:48   ` David Herrmann
2016-09-02  6:11     ` Daniel Vetter
2016-08-22 20:25 ` [PATCH v4 4/5] drm: simpledrm: add fbdev fallback support Noralf Trønnes
2016-08-23  6:10   ` Daniel Vetter
2016-08-22 20:25 ` [PATCH v4 5/5] drm: simpledrm: honour remove_conflicting_framebuffers() Noralf Trønnes
2016-08-23 12:41   ` Daniel Vetter
2016-08-23 17:52     ` Noralf Trønnes
2016-08-23 18:01       ` Daniel Vetter
2016-08-23 19:22         ` Noralf Trønnes

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).