All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/10] Generic USB Display driver
@ 2020-04-29 12:48 ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

Hi,

A while back I had the idea to turn a Raspberry Pi Zero into a $5
USB to HDMI/SDTV/DSI/DPI display adapter.

This series adds a USB host driver and a device/gadget driver to achieve
that.

The reason for calling it 'Generic' is so anyone can make a USB
display/adapter against this driver, all that's needed is to add a USB
vid:pid. I was hoping to have someone working on a microcontroller based
USB display by now, but unfortunately that has been delayed. It would
have been nice to have a microcontroller implementation to ensure that I
haven't made things unnecessary difficult to implement.

Performance
The one thing that decides how useful this all is, is how smooth an
experience it gives. My hope was that it should not be noticeably laggy
with ordinary office use on 1920x1080@RG16. I'm pleased to see that it's
also possible to watch youtube movies, although not in fullscreen.

Some of the main factors that affects performance:
- Display resolution
- Userspace providing damage reports (FB_DAMAGE_CLIPS or
DRM_IOCTL_MODE_DIRTYFB)
- Color depth (DRM_CAP_DUMB_PREFERRED_DEPTH = 16 if RGB565)
- How well the frames compress (lz4)
- Gadget device memory bandwidth, CPU power for decompression
- (Big endian hosts will have to do byte swapping on the frames)

I've tested these:
- xorg-server on Pi4. This was nice and smooth since it uses
DRM_IOCTL_MODE_DIRTYFB and honours DRM_CAP_DUMB_PREFERRED_DEPTH.
- Ubuntu 20.04 GNOME on x86. This was useable, but not so good for
movies. GNOME doesn't look at DRM_CAP_DUMB_PREFERRED_DEPTH and doesn't
set FB_DAMAGE_CLIPS on the pageflips.

I've made a short video to show what it looks like[1].

I have used a Pi4 as the gadget during development since it has much
better memory bandwith (4000 vs 200 MBps)[2] and CPU than the PiZ. They
both have the same gadget controller (dwc2).

I did an RFC [3] of this 2 months ago where I looked at doing a Multi
Function USB device. I gave up on that when I realised how much work the
review process would be. So I stripped down to my original idea. I have
made sure that the drivers will tolerate another USB interface of type
VENDOR, so it's still possible for the display to be part of a multi
function device.

Noralf.

[1] https://youtu.be/AhGZWwUm8JU
[2] https://magpi.raspberrypi.org/articles/raspberry-pi-specs-benchmarks
[3] https://patchwork.freedesktop.org/series/73508/


Noralf Trønnes (10):
  backlight: Add backlight_device_get_by_name()
  drm: Add backlight helper
  drm/client: Add drm_client_init_from_id()
  drm/client: Add drm_client_framebuffer_flush()
  drm/client: Add drm_client_modeset_check()
  drm/client: Add a way to set modeset, properties and rotation
  drm/format-helper: Add drm_fb_swab()
  drm: Add Generic USB Display driver
  drm/gud: Add functionality for the USB gadget side
  usb: gadget: function: Add Generic USB Display support

 .../ABI/testing/configfs-usb-gadget-gud_drm   |   10 +
 Documentation/gpu/drm-kms-helpers.rst         |    6 +
 MAINTAINERS                                   |   10 +
 drivers/gpu/drm/Kconfig                       |    9 +
 drivers/gpu/drm/Makefile                      |    2 +
 drivers/gpu/drm/drm_backlight_helper.c        |  154 +++
 drivers/gpu/drm/drm_client.c                  |   79 +-
 drivers/gpu/drm/drm_client_modeset.c          |  174 ++-
 drivers/gpu/drm/drm_format_helper.c           |   61 +-
 drivers/gpu/drm/drm_mipi_dbi.c                |    2 +-
 drivers/gpu/drm/gud/Kconfig                   |   20 +
 drivers/gpu/drm/gud/Makefile                  |    5 +
 drivers/gpu/drm/gud/gud_drm_connector.c       |  629 +++++++++
 drivers/gpu/drm/gud/gud_drm_drv.c             |  624 +++++++++
 drivers/gpu/drm/gud/gud_drm_gadget.c          | 1169 +++++++++++++++++
 drivers/gpu/drm/gud/gud_drm_internal.h        |   66 +
 drivers/gpu/drm/gud/gud_drm_pipe.c            |  423 ++++++
 drivers/usb/gadget/Kconfig                    |   12 +
 drivers/usb/gadget/function/Makefile          |    2 +
 drivers/usb/gadget/function/f_gud_drm.c       |  678 ++++++++++
 drivers/video/backlight/backlight.c           |   21 +
 include/drm/drm_backlight_helper.h            |    9 +
 include/drm/drm_client.h                      |   44 +-
 include/drm/drm_connector.h                   |   10 +
 include/drm/drm_format_helper.h               |    4 +-
 include/drm/gud_drm.h                         |  369 ++++++
 include/linux/backlight.h                     |    1 +
 27 files changed, 4563 insertions(+), 30 deletions(-)
 create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-gud_drm
 create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
 create mode 100644 drivers/gpu/drm/gud/Kconfig
 create mode 100644 drivers/gpu/drm/gud/Makefile
 create mode 100644 drivers/gpu/drm/gud/gud_drm_connector.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_drv.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_gadget.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_internal.h
 create mode 100644 drivers/gpu/drm/gud/gud_drm_pipe.c
 create mode 100644 drivers/usb/gadget/function/f_gud_drm.c
 create mode 100644 include/drm/drm_backlight_helper.h
 create mode 100644 include/drm/gud_drm.h

-- 
2.23.0


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

* [PATCH 00/10] Generic USB Display driver
@ 2020-04-29 12:48 ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

Hi,

A while back I had the idea to turn a Raspberry Pi Zero into a $5
USB to HDMI/SDTV/DSI/DPI display adapter.

This series adds a USB host driver and a device/gadget driver to achieve
that.

The reason for calling it 'Generic' is so anyone can make a USB
display/adapter against this driver, all that's needed is to add a USB
vid:pid. I was hoping to have someone working on a microcontroller based
USB display by now, but unfortunately that has been delayed. It would
have been nice to have a microcontroller implementation to ensure that I
haven't made things unnecessary difficult to implement.

Performance
The one thing that decides how useful this all is, is how smooth an
experience it gives. My hope was that it should not be noticeably laggy
with ordinary office use on 1920x1080@RG16. I'm pleased to see that it's
also possible to watch youtube movies, although not in fullscreen.

Some of the main factors that affects performance:
- Display resolution
- Userspace providing damage reports (FB_DAMAGE_CLIPS or
DRM_IOCTL_MODE_DIRTYFB)
- Color depth (DRM_CAP_DUMB_PREFERRED_DEPTH = 16 if RGB565)
- How well the frames compress (lz4)
- Gadget device memory bandwidth, CPU power for decompression
- (Big endian hosts will have to do byte swapping on the frames)

I've tested these:
- xorg-server on Pi4. This was nice and smooth since it uses
DRM_IOCTL_MODE_DIRTYFB and honours DRM_CAP_DUMB_PREFERRED_DEPTH.
- Ubuntu 20.04 GNOME on x86. This was useable, but not so good for
movies. GNOME doesn't look at DRM_CAP_DUMB_PREFERRED_DEPTH and doesn't
set FB_DAMAGE_CLIPS on the pageflips.

I've made a short video to show what it looks like[1].

I have used a Pi4 as the gadget during development since it has much
better memory bandwith (4000 vs 200 MBps)[2] and CPU than the PiZ. They
both have the same gadget controller (dwc2).

I did an RFC [3] of this 2 months ago where I looked at doing a Multi
Function USB device. I gave up on that when I realised how much work the
review process would be. So I stripped down to my original idea. I have
made sure that the drivers will tolerate another USB interface of type
VENDOR, so it's still possible for the display to be part of a multi
function device.

Noralf.

[1] https://youtu.be/AhGZWwUm8JU
[2] https://magpi.raspberrypi.org/articles/raspberry-pi-specs-benchmarks
[3] https://patchwork.freedesktop.org/series/73508/


Noralf Trønnes (10):
  backlight: Add backlight_device_get_by_name()
  drm: Add backlight helper
  drm/client: Add drm_client_init_from_id()
  drm/client: Add drm_client_framebuffer_flush()
  drm/client: Add drm_client_modeset_check()
  drm/client: Add a way to set modeset, properties and rotation
  drm/format-helper: Add drm_fb_swab()
  drm: Add Generic USB Display driver
  drm/gud: Add functionality for the USB gadget side
  usb: gadget: function: Add Generic USB Display support

 .../ABI/testing/configfs-usb-gadget-gud_drm   |   10 +
 Documentation/gpu/drm-kms-helpers.rst         |    6 +
 MAINTAINERS                                   |   10 +
 drivers/gpu/drm/Kconfig                       |    9 +
 drivers/gpu/drm/Makefile                      |    2 +
 drivers/gpu/drm/drm_backlight_helper.c        |  154 +++
 drivers/gpu/drm/drm_client.c                  |   79 +-
 drivers/gpu/drm/drm_client_modeset.c          |  174 ++-
 drivers/gpu/drm/drm_format_helper.c           |   61 +-
 drivers/gpu/drm/drm_mipi_dbi.c                |    2 +-
 drivers/gpu/drm/gud/Kconfig                   |   20 +
 drivers/gpu/drm/gud/Makefile                  |    5 +
 drivers/gpu/drm/gud/gud_drm_connector.c       |  629 +++++++++
 drivers/gpu/drm/gud/gud_drm_drv.c             |  624 +++++++++
 drivers/gpu/drm/gud/gud_drm_gadget.c          | 1169 +++++++++++++++++
 drivers/gpu/drm/gud/gud_drm_internal.h        |   66 +
 drivers/gpu/drm/gud/gud_drm_pipe.c            |  423 ++++++
 drivers/usb/gadget/Kconfig                    |   12 +
 drivers/usb/gadget/function/Makefile          |    2 +
 drivers/usb/gadget/function/f_gud_drm.c       |  678 ++++++++++
 drivers/video/backlight/backlight.c           |   21 +
 include/drm/drm_backlight_helper.h            |    9 +
 include/drm/drm_client.h                      |   44 +-
 include/drm/drm_connector.h                   |   10 +
 include/drm/drm_format_helper.h               |    4 +-
 include/drm/gud_drm.h                         |  369 ++++++
 include/linux/backlight.h                     |    1 +
 27 files changed, 4563 insertions(+), 30 deletions(-)
 create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-gud_drm
 create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
 create mode 100644 drivers/gpu/drm/gud/Kconfig
 create mode 100644 drivers/gpu/drm/gud/Makefile
 create mode 100644 drivers/gpu/drm/gud/gud_drm_connector.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_drv.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_gadget.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_internal.h
 create mode 100644 drivers/gpu/drm/gud/gud_drm_pipe.c
 create mode 100644 drivers/usb/gadget/function/f_gud_drm.c
 create mode 100644 include/drm/drm_backlight_helper.h
 create mode 100644 include/drm/gud_drm.h

-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb
  Cc: Noralf Trønnes, Lee Jones, Daniel Thompson, Jingoo Han

Add a way to lookup a backlight device based on its name.
Will be used by a USB display gadget getting the name from configfs.

Cc: Lee Jones <lee.jones@linaro.org>
Cc: Daniel Thompson <daniel.thompson@linaro.org>
Cc: Jingoo Han <jingoohan1@gmail.com>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
 include/linux/backlight.h           |  1 +
 2 files changed, 22 insertions(+)

diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
index cac3e35d7630..92d80aa0c0ef 100644
--- a/drivers/video/backlight/backlight.c
+++ b/drivers/video/backlight/backlight.c
@@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
 }
 EXPORT_SYMBOL(backlight_device_get_by_type);
 
+/**
+ * backlight_device_get_by_name - Get backlight device by name
+ * @name: Device name
+ *
+ * This function looks up a backlight device by its name. It obtains a reference
+ * on the backlight device and it is the caller's responsibility to drop the
+ * reference by calling backlight_put().
+ *
+ * Returns:
+ * A pointer to the backlight device if found, otherwise NULL.
+ */
+struct backlight_device *backlight_device_get_by_name(const char *name)
+{
+	struct device *dev;
+
+	dev = class_find_device_by_name(backlight_class, name);
+
+	return dev ? to_backlight_device(dev) : NULL;
+}
+EXPORT_SYMBOL(backlight_device_get_by_name);
+
 /**
  * backlight_device_unregister - unregisters a backlight device object.
  * @bd: the backlight device object to be unregistered and freed.
diff --git a/include/linux/backlight.h b/include/linux/backlight.h
index c7d6b2e8c3b5..56e4580d4f55 100644
--- a/include/linux/backlight.h
+++ b/include/linux/backlight.h
@@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
 extern int backlight_register_notifier(struct notifier_block *nb);
 extern int backlight_unregister_notifier(struct notifier_block *nb);
 extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
+struct backlight_device *backlight_device_get_by_name(const char *name);
 extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
 
 #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)
-- 
2.23.0


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

* [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Jingoo Han, Daniel Thompson, Lee Jones

Add a way to lookup a backlight device based on its name.
Will be used by a USB display gadget getting the name from configfs.

Cc: Lee Jones <lee.jones@linaro.org>
Cc: Daniel Thompson <daniel.thompson@linaro.org>
Cc: Jingoo Han <jingoohan1@gmail.com>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
 include/linux/backlight.h           |  1 +
 2 files changed, 22 insertions(+)

diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
index cac3e35d7630..92d80aa0c0ef 100644
--- a/drivers/video/backlight/backlight.c
+++ b/drivers/video/backlight/backlight.c
@@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
 }
 EXPORT_SYMBOL(backlight_device_get_by_type);
 
+/**
+ * backlight_device_get_by_name - Get backlight device by name
+ * @name: Device name
+ *
+ * This function looks up a backlight device by its name. It obtains a reference
+ * on the backlight device and it is the caller's responsibility to drop the
+ * reference by calling backlight_put().
+ *
+ * Returns:
+ * A pointer to the backlight device if found, otherwise NULL.
+ */
+struct backlight_device *backlight_device_get_by_name(const char *name)
+{
+	struct device *dev;
+
+	dev = class_find_device_by_name(backlight_class, name);
+
+	return dev ? to_backlight_device(dev) : NULL;
+}
+EXPORT_SYMBOL(backlight_device_get_by_name);
+
 /**
  * backlight_device_unregister - unregisters a backlight device object.
  * @bd: the backlight device object to be unregistered and freed.
diff --git a/include/linux/backlight.h b/include/linux/backlight.h
index c7d6b2e8c3b5..56e4580d4f55 100644
--- a/include/linux/backlight.h
+++ b/include/linux/backlight.h
@@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
 extern int backlight_register_notifier(struct notifier_block *nb);
 extern int backlight_unregister_notifier(struct notifier_block *nb);
 extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
+struct backlight_device *backlight_device_get_by_name(const char *name);
 extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
 
 #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 02/10] drm: Add backlight helper
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb
  Cc: Noralf Trønnes, Hans de Goede, Jani Nikula, Martin Peres,
	Daniel Thompson

This adds a function that creates a backlight device for a connector.
It does not deal with the KMS backlight ABI proposition[1] to add a
connector property. It only takes the current best practise to standardise
the creation of a backlight device for DRM drivers while we wait for the
property.

The brightness value is set using a connector state variable and an atomic
commit.

I have looked through some of the backlight users and this is what I've found:

GNOME [2]
---------

Brightness range: 0-100
Scale: Assumes perceptual

Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
Can connect connector and backlight using the sysfs device.

KDE [3]
-------

Brightness range: 0-100
Scale: Assumes perceptual

Weston [4]
----------

Brightness range: 0-255
Scale: Assumes perceptual

Chromium OS [5]
---------------

Brightness range: 0-100
Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)

xserver [6]
-----------

Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
Scale: Assumes perceptual

The builtin modesetting driver[7] does not support Backlight, Intel[8] does.

[1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
[2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
[3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
[4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
[5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
[6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
[7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
[8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c

Cc: Hans de Goede <hdegoede@redhat.com>
Cc: Jani Nikula <jani.nikula@linux.intel.com>
Cc: Martin Peres <martin.peres@linux.intel.com>
Cc: Daniel Thompson <daniel.thompson@linaro.org>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 Documentation/gpu/drm-kms-helpers.rst  |   6 +
 drivers/gpu/drm/Kconfig                |   7 ++
 drivers/gpu/drm/Makefile               |   1 +
 drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
 include/drm/drm_backlight_helper.h     |   9 ++
 include/drm/drm_connector.h            |  10 ++
 6 files changed, 187 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
 create mode 100644 include/drm/drm_backlight_helper.h

diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 9668a7fe2408..29a2f4b49fd2 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
 
 .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
    :export:
+
+Backlight Helper Reference
+==========================
+
+.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
+   :export:
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index d0aa6cff2e02..f6e13e18c9ca 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
 	help
 	  Choose this if you need the GEM shmem helper functions
 
+config DRM_BACKLIGHT_HELPER
+	bool
+	depends on DRM
+	select BACKLIGHT_CLASS_DEVICE
+	help
+	  Choose this if you need the backlight device helper functions
+
 config DRM_VM
 	bool
 	depends on DRM && MMU
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6493088a0fdd..0d17662dde0a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
 drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
 drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
 drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
+drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
 
 obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
 obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
new file mode 100644
index 000000000000..06e6a75d1d0a
--- /dev/null
+++ b/drivers/gpu/drm/drm_backlight_helper.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+
+static int drm_backlight_update_status(struct backlight_device *bd)
+{
+	struct drm_connector *connector = bl_get_data(bd);
+	struct drm_connector_state *connector_state;
+	struct drm_device *dev = connector->dev;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_state *state;
+	int ret;
+
+	state = drm_atomic_state_alloc(dev);
+	if (!state)
+		return -ENOMEM;
+
+	drm_modeset_acquire_init(&ctx, 0);
+	state->acquire_ctx = &ctx;
+retry:
+	connector_state = drm_atomic_get_connector_state(state, connector);
+	if (IS_ERR(connector_state)) {
+		ret = PTR_ERR(connector_state);
+		goto out;
+	}
+
+	connector_state->backlight_brightness = bd->props.brightness;
+
+	ret = drm_atomic_commit(state);
+out:
+	if (ret == -EDEADLK) {
+		drm_atomic_state_clear(state);
+		drm_modeset_backoff(&ctx);
+		goto retry;
+	}
+
+	drm_atomic_state_put(state);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+
+	return ret;
+}
+
+static int drm_backlight_get_brightness(struct backlight_device *bd)
+{
+	struct drm_connector *connector = bl_get_data(bd);
+	struct drm_device *dev = connector->dev;
+	int brightness;
+
+	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+	brightness = connector->state->backlight_brightness;
+	drm_modeset_unlock(&dev->mode_config.connection_mutex);
+
+	return brightness;
+}
+
+static const struct backlight_ops drm_backlight_ops = {
+	.get_brightness = drm_backlight_get_brightness,
+	.update_status	= drm_backlight_update_status,
+};
+
+/* Can be exported for drivers carrying a legacy name */
+static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
+{
+	struct backlight_device *bd;
+	const struct backlight_properties props = {
+		.type = BACKLIGHT_RAW,
+		.scale = BACKLIGHT_SCALE_NON_LINEAR,
+		.max_brightness = 100,
+	};
+
+	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
+		    !drm_drv_uses_atomic_modeset(connector->dev) ||
+		    !connector->kdev))
+		return -EINVAL;
+
+	bd = backlight_device_register(name, connector->kdev, connector,
+				       &drm_backlight_ops, &props);
+	if (IS_ERR(bd))
+		return PTR_ERR(bd);
+
+	connector->backlight = bd;
+
+	return 0;
+}
+
+/**
+ * drm_backlight_register() - Register a backlight device for a connector
+ * @connector: Connector
+ *
+ * This function registers a backlight device for @connector with the following
+ * characteristics:
+ *
+ * - The connector sysfs device is used as a parent device for the backlight device.
+ *   Userspace can use this to connect backlight device and connector.
+ * - Name will be on the form: **card0-HDMI-A-1-backlight**
+ * - Type is "raw"
+ * - Scale is "non-linear" (perceptual)
+ * - Max brightness is 100 giving a range of 0-100 inclusive
+ * - Reading sysfs **brightness** returns the backlight device property
+ * - Reading sysfs **actual_brightness** returns the connector state value
+ * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
+ *   be used to control power.
+ * - Backlight device suspend/resume events are ignored.
+ *
+ * Note:
+ *
+ * Brightness zero should not turn off backlight it should be the minimum
+ * brightness, DPMS handles power.
+ *
+ * This function must be called from &drm_connector_funcs->late_register() since
+ * it depends on the sysfs device.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_backlight_register(struct drm_connector *connector)
+{
+	const char *name = NULL;
+	int ret;
+
+	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
+			 connector->dev->primary->index, connector->name);
+	if (!name)
+		return -ENOMEM;
+
+	ret = drm_backlight_register_with_name(connector, name);
+	kfree(name);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_backlight_register);
+
+/**
+ * drm_backlight_unregister() - Unregister backlight device
+ * @connector: Connector
+ *
+ * Unregister a backlight device. This must be called from the
+ * &drm_connector_funcs->early_unregister() callback.
+ */
+void drm_backlight_unregister(struct drm_connector *connector)
+{
+	backlight_device_unregister(connector->backlight);
+}
+EXPORT_SYMBOL(drm_backlight_unregister);
diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
new file mode 100644
index 000000000000..4151b66eb0b4
--- /dev/null
+++ b/include/drm/drm_backlight_helper.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+
+#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
+#define _LINUX_DRM_BACKLIGHT_HELPER_H
+
+int drm_backlight_register(struct drm_connector *connector);
+void drm_backlight_unregister(struct drm_connector *connector);
+
+#endif
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 221910948b37..ce678b694f45 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -32,6 +32,7 @@
 
 #include <uapi/drm/drm_mode.h>
 
+struct backlight_device;
 struct drm_connector_helper_funcs;
 struct drm_modeset_acquire_ctx;
 struct drm_device;
@@ -656,6 +657,12 @@ struct drm_connector_state {
 	 */
 	u8 max_bpc;
 
+	/**
+	 * @backlight_brightness: Brightness value of the connector backlight
+	 * device. See drm_backlight_register().
+	 */
+	u8 backlight_brightness;
+
 	/**
 	 * @hdr_output_metadata:
 	 * DRM blob property for HDR output metadata
@@ -1422,6 +1429,9 @@ struct drm_connector {
 
 	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
 	struct hdr_sink_metadata hdr_sink_metadata;
+
+	/** @backlight: Backlight device. See drm_backlight_register() */
+	struct backlight_device *backlight;
 };
 
 #define obj_to_connector(x) container_of(x, struct drm_connector, base)
-- 
2.23.0


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

* [PATCH 02/10] drm: Add backlight helper
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Hans de Goede, Daniel Thompson

This adds a function that creates a backlight device for a connector.
It does not deal with the KMS backlight ABI proposition[1] to add a
connector property. It only takes the current best practise to standardise
the creation of a backlight device for DRM drivers while we wait for the
property.

The brightness value is set using a connector state variable and an atomic
commit.

I have looked through some of the backlight users and this is what I've found:

GNOME [2]
---------

Brightness range: 0-100
Scale: Assumes perceptual

Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
Can connect connector and backlight using the sysfs device.

KDE [3]
-------

Brightness range: 0-100
Scale: Assumes perceptual

Weston [4]
----------

Brightness range: 0-255
Scale: Assumes perceptual

Chromium OS [5]
---------------

Brightness range: 0-100
Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)

xserver [6]
-----------

Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
Scale: Assumes perceptual

The builtin modesetting driver[7] does not support Backlight, Intel[8] does.

[1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
[2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
[3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
[4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
[5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
[6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
[7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
[8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c

Cc: Hans de Goede <hdegoede@redhat.com>
Cc: Jani Nikula <jani.nikula@linux.intel.com>
Cc: Martin Peres <martin.peres@linux.intel.com>
Cc: Daniel Thompson <daniel.thompson@linaro.org>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 Documentation/gpu/drm-kms-helpers.rst  |   6 +
 drivers/gpu/drm/Kconfig                |   7 ++
 drivers/gpu/drm/Makefile               |   1 +
 drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
 include/drm/drm_backlight_helper.h     |   9 ++
 include/drm/drm_connector.h            |  10 ++
 6 files changed, 187 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
 create mode 100644 include/drm/drm_backlight_helper.h

diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 9668a7fe2408..29a2f4b49fd2 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
 
 .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
    :export:
+
+Backlight Helper Reference
+==========================
+
+.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
+   :export:
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index d0aa6cff2e02..f6e13e18c9ca 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
 	help
 	  Choose this if you need the GEM shmem helper functions
 
+config DRM_BACKLIGHT_HELPER
+	bool
+	depends on DRM
+	select BACKLIGHT_CLASS_DEVICE
+	help
+	  Choose this if you need the backlight device helper functions
+
 config DRM_VM
 	bool
 	depends on DRM && MMU
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6493088a0fdd..0d17662dde0a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
 drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
 drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
 drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
+drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
 
 obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
 obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
new file mode 100644
index 000000000000..06e6a75d1d0a
--- /dev/null
+++ b/drivers/gpu/drm/drm_backlight_helper.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+
+static int drm_backlight_update_status(struct backlight_device *bd)
+{
+	struct drm_connector *connector = bl_get_data(bd);
+	struct drm_connector_state *connector_state;
+	struct drm_device *dev = connector->dev;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_state *state;
+	int ret;
+
+	state = drm_atomic_state_alloc(dev);
+	if (!state)
+		return -ENOMEM;
+
+	drm_modeset_acquire_init(&ctx, 0);
+	state->acquire_ctx = &ctx;
+retry:
+	connector_state = drm_atomic_get_connector_state(state, connector);
+	if (IS_ERR(connector_state)) {
+		ret = PTR_ERR(connector_state);
+		goto out;
+	}
+
+	connector_state->backlight_brightness = bd->props.brightness;
+
+	ret = drm_atomic_commit(state);
+out:
+	if (ret == -EDEADLK) {
+		drm_atomic_state_clear(state);
+		drm_modeset_backoff(&ctx);
+		goto retry;
+	}
+
+	drm_atomic_state_put(state);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+
+	return ret;
+}
+
+static int drm_backlight_get_brightness(struct backlight_device *bd)
+{
+	struct drm_connector *connector = bl_get_data(bd);
+	struct drm_device *dev = connector->dev;
+	int brightness;
+
+	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+	brightness = connector->state->backlight_brightness;
+	drm_modeset_unlock(&dev->mode_config.connection_mutex);
+
+	return brightness;
+}
+
+static const struct backlight_ops drm_backlight_ops = {
+	.get_brightness = drm_backlight_get_brightness,
+	.update_status	= drm_backlight_update_status,
+};
+
+/* Can be exported for drivers carrying a legacy name */
+static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
+{
+	struct backlight_device *bd;
+	const struct backlight_properties props = {
+		.type = BACKLIGHT_RAW,
+		.scale = BACKLIGHT_SCALE_NON_LINEAR,
+		.max_brightness = 100,
+	};
+
+	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
+		    !drm_drv_uses_atomic_modeset(connector->dev) ||
+		    !connector->kdev))
+		return -EINVAL;
+
+	bd = backlight_device_register(name, connector->kdev, connector,
+				       &drm_backlight_ops, &props);
+	if (IS_ERR(bd))
+		return PTR_ERR(bd);
+
+	connector->backlight = bd;
+
+	return 0;
+}
+
+/**
+ * drm_backlight_register() - Register a backlight device for a connector
+ * @connector: Connector
+ *
+ * This function registers a backlight device for @connector with the following
+ * characteristics:
+ *
+ * - The connector sysfs device is used as a parent device for the backlight device.
+ *   Userspace can use this to connect backlight device and connector.
+ * - Name will be on the form: **card0-HDMI-A-1-backlight**
+ * - Type is "raw"
+ * - Scale is "non-linear" (perceptual)
+ * - Max brightness is 100 giving a range of 0-100 inclusive
+ * - Reading sysfs **brightness** returns the backlight device property
+ * - Reading sysfs **actual_brightness** returns the connector state value
+ * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
+ *   be used to control power.
+ * - Backlight device suspend/resume events are ignored.
+ *
+ * Note:
+ *
+ * Brightness zero should not turn off backlight it should be the minimum
+ * brightness, DPMS handles power.
+ *
+ * This function must be called from &drm_connector_funcs->late_register() since
+ * it depends on the sysfs device.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_backlight_register(struct drm_connector *connector)
+{
+	const char *name = NULL;
+	int ret;
+
+	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
+			 connector->dev->primary->index, connector->name);
+	if (!name)
+		return -ENOMEM;
+
+	ret = drm_backlight_register_with_name(connector, name);
+	kfree(name);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_backlight_register);
+
+/**
+ * drm_backlight_unregister() - Unregister backlight device
+ * @connector: Connector
+ *
+ * Unregister a backlight device. This must be called from the
+ * &drm_connector_funcs->early_unregister() callback.
+ */
+void drm_backlight_unregister(struct drm_connector *connector)
+{
+	backlight_device_unregister(connector->backlight);
+}
+EXPORT_SYMBOL(drm_backlight_unregister);
diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
new file mode 100644
index 000000000000..4151b66eb0b4
--- /dev/null
+++ b/include/drm/drm_backlight_helper.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+
+#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
+#define _LINUX_DRM_BACKLIGHT_HELPER_H
+
+int drm_backlight_register(struct drm_connector *connector);
+void drm_backlight_unregister(struct drm_connector *connector);
+
+#endif
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 221910948b37..ce678b694f45 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -32,6 +32,7 @@
 
 #include <uapi/drm/drm_mode.h>
 
+struct backlight_device;
 struct drm_connector_helper_funcs;
 struct drm_modeset_acquire_ctx;
 struct drm_device;
@@ -656,6 +657,12 @@ struct drm_connector_state {
 	 */
 	u8 max_bpc;
 
+	/**
+	 * @backlight_brightness: Brightness value of the connector backlight
+	 * device. See drm_backlight_register().
+	 */
+	u8 backlight_brightness;
+
 	/**
 	 * @hdr_output_metadata:
 	 * DRM blob property for HDR output metadata
@@ -1422,6 +1429,9 @@ struct drm_connector {
 
 	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
 	struct hdr_sink_metadata hdr_sink_metadata;
+
+	/** @backlight: Backlight device. See drm_backlight_register() */
+	struct backlight_device *backlight;
 };
 
 #define obj_to_connector(x) container_of(x, struct drm_connector, base)
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 03/10] drm/client: Add drm_client_init_from_id()
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

drm_client_init_from_id() provides a way for clients to add a client based
on the minor. drm_client_register() is changed to return whether it was
registered or not depending on the unplugged status of the DRM device.
Its only caller drm_fbdev_generic_setup() runs inside probe() so it
doesn't have to check.

v2:
- Move drm_client_modeset_set() to a separate patch with added functions.
- Previous version had drm_client_init_from_id() call
  drm_client_register(). This put the client in a position where it could
  receive hotplugs during init in addition to akward error paths. Instead
  let drm_client_register() return status so clients can know if the DRM
  device is gone or not.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_client.c | 48 +++++++++++++++++++++++++++++++++++-
 include/drm/drm_client.h     |  4 ++-
 2 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index b031b45aa8ef..cb5ee9f1ffaa 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -112,6 +112,40 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
 }
 EXPORT_SYMBOL(drm_client_init);
 
+/**
+ * drm_client_init_from_id - Initialise a DRM client
+ * @minor_id: DRM minor id
+ * @client: DRM client
+ * @name: Client name
+ * @funcs: DRM client functions (optional)
+ *
+ * This function looks up the drm_device using the minor id and initializes the client.
+ *
+ * See drm_client_init() and drm_client_register().
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client,
+			    const char *name, const struct drm_client_funcs *funcs)
+{
+	struct drm_minor *minor;
+	int ret;
+
+	minor = drm_minor_acquire(minor_id);
+	if (IS_ERR(minor))
+		return PTR_ERR(minor);
+
+	mutex_lock(&minor->dev->clientlist_mutex);
+	ret = drm_client_init(minor->dev, client, name, funcs);
+	mutex_unlock(&minor->dev->clientlist_mutex);
+
+	drm_minor_release(minor);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_init_from_id);
+
 /**
  * drm_client_register - Register client
  * @client: DRM client
@@ -121,14 +155,26 @@ EXPORT_SYMBOL(drm_client_init);
  * drm_client_register() it is no longer permissible to call drm_client_release()
  * directly (outside the unregister callback), instead cleanup will happen
  * automatically on driver unload.
+ *
+ * Returns:
+ * True if the client has been registered, false if the DRM device has already
+ * been unregistered.
  */
-void drm_client_register(struct drm_client_dev *client)
+bool drm_client_register(struct drm_client_dev *client)
 {
 	struct drm_device *dev = client->dev;
+	int idx;
+
+	if (!drm_dev_enter(client->dev, &idx))
+		return false;
 
 	mutex_lock(&dev->clientlist_mutex);
 	list_add(&client->list, &dev->clientlist);
 	mutex_unlock(&dev->clientlist_mutex);
+
+	drm_dev_exit(idx);
+
+	return true;
 }
 EXPORT_SYMBOL(drm_client_register);
 
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index 3ed5dee899fd..bbb5689fa9a8 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -109,8 +109,10 @@ struct drm_client_dev {
 
 int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
 		    const char *name, const struct drm_client_funcs *funcs);
+int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client,
+			    const char *name, const struct drm_client_funcs *funcs);
 void drm_client_release(struct drm_client_dev *client);
-void drm_client_register(struct drm_client_dev *client);
+bool drm_client_register(struct drm_client_dev *client);
 
 void drm_client_dev_unregister(struct drm_device *dev);
 void drm_client_dev_hotplug(struct drm_device *dev);
-- 
2.23.0


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

* [PATCH 03/10] drm/client: Add drm_client_init_from_id()
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

drm_client_init_from_id() provides a way for clients to add a client based
on the minor. drm_client_register() is changed to return whether it was
registered or not depending on the unplugged status of the DRM device.
Its only caller drm_fbdev_generic_setup() runs inside probe() so it
doesn't have to check.

v2:
- Move drm_client_modeset_set() to a separate patch with added functions.
- Previous version had drm_client_init_from_id() call
  drm_client_register(). This put the client in a position where it could
  receive hotplugs during init in addition to akward error paths. Instead
  let drm_client_register() return status so clients can know if the DRM
  device is gone or not.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_client.c | 48 +++++++++++++++++++++++++++++++++++-
 include/drm/drm_client.h     |  4 ++-
 2 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index b031b45aa8ef..cb5ee9f1ffaa 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -112,6 +112,40 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
 }
 EXPORT_SYMBOL(drm_client_init);
 
+/**
+ * drm_client_init_from_id - Initialise a DRM client
+ * @minor_id: DRM minor id
+ * @client: DRM client
+ * @name: Client name
+ * @funcs: DRM client functions (optional)
+ *
+ * This function looks up the drm_device using the minor id and initializes the client.
+ *
+ * See drm_client_init() and drm_client_register().
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client,
+			    const char *name, const struct drm_client_funcs *funcs)
+{
+	struct drm_minor *minor;
+	int ret;
+
+	minor = drm_minor_acquire(minor_id);
+	if (IS_ERR(minor))
+		return PTR_ERR(minor);
+
+	mutex_lock(&minor->dev->clientlist_mutex);
+	ret = drm_client_init(minor->dev, client, name, funcs);
+	mutex_unlock(&minor->dev->clientlist_mutex);
+
+	drm_minor_release(minor);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_init_from_id);
+
 /**
  * drm_client_register - Register client
  * @client: DRM client
@@ -121,14 +155,26 @@ EXPORT_SYMBOL(drm_client_init);
  * drm_client_register() it is no longer permissible to call drm_client_release()
  * directly (outside the unregister callback), instead cleanup will happen
  * automatically on driver unload.
+ *
+ * Returns:
+ * True if the client has been registered, false if the DRM device has already
+ * been unregistered.
  */
-void drm_client_register(struct drm_client_dev *client)
+bool drm_client_register(struct drm_client_dev *client)
 {
 	struct drm_device *dev = client->dev;
+	int idx;
+
+	if (!drm_dev_enter(client->dev, &idx))
+		return false;
 
 	mutex_lock(&dev->clientlist_mutex);
 	list_add(&client->list, &dev->clientlist);
 	mutex_unlock(&dev->clientlist_mutex);
+
+	drm_dev_exit(idx);
+
+	return true;
 }
 EXPORT_SYMBOL(drm_client_register);
 
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index 3ed5dee899fd..bbb5689fa9a8 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -109,8 +109,10 @@ struct drm_client_dev {
 
 int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
 		    const char *name, const struct drm_client_funcs *funcs);
+int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client,
+			    const char *name, const struct drm_client_funcs *funcs);
 void drm_client_release(struct drm_client_dev *client);
-void drm_client_register(struct drm_client_dev *client);
+bool drm_client_register(struct drm_client_dev *client);
 
 void drm_client_dev_unregister(struct drm_device *dev);
 void drm_client_dev_hotplug(struct drm_device *dev);
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 04/10] drm/client: Add drm_client_framebuffer_flush()
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

Some drivers need explicit flushing of buffer changes, add a function
that does that.

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

diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index cb5ee9f1ffaa..8dbc2ecdcaea 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -483,6 +483,37 @@ void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
 }
 EXPORT_SYMBOL(drm_client_framebuffer_delete);
 
+/**
+ * drm_client_framebuffer_flush - Manually flush client framebuffer
+ * @buffer: DRM client buffer (can be NULL)
+ * @rect: Damage rectangle (if NULL flushes all)
+ *
+ * This calls &drm_framebuffer_funcs->dirty (if present) to flush buffer changes
+ * for drivers that need it.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect)
+{
+	struct drm_clip_rect clip_rect, *clip = NULL;
+
+	if (!buffer || !buffer->fb || !buffer->fb->funcs->dirty)
+		return 0;
+
+	if (rect) {
+		clip = &clip_rect;
+		clip->x1 = rect->x1;
+		clip->y1 = rect->y1;
+		clip->x2 = rect->x2;
+		clip->y2 = rect->y2;
+	}
+
+	return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
+					0, 0, clip, clip ? 1 : 0);
+}
+EXPORT_SYMBOL(drm_client_framebuffer_flush);
+
 #ifdef CONFIG_DEBUG_FS
 static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
 {
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index bbb5689fa9a8..6ef5364d6dfb 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -156,6 +156,7 @@ struct drm_client_buffer {
 struct drm_client_buffer *
 drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format);
 void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
+int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect);
 void *drm_client_buffer_vmap(struct drm_client_buffer *buffer);
 void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
 
-- 
2.23.0


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

* [PATCH 04/10] drm/client: Add drm_client_framebuffer_flush()
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

Some drivers need explicit flushing of buffer changes, add a function
that does that.

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

diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index cb5ee9f1ffaa..8dbc2ecdcaea 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -483,6 +483,37 @@ void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
 }
 EXPORT_SYMBOL(drm_client_framebuffer_delete);
 
+/**
+ * drm_client_framebuffer_flush - Manually flush client framebuffer
+ * @buffer: DRM client buffer (can be NULL)
+ * @rect: Damage rectangle (if NULL flushes all)
+ *
+ * This calls &drm_framebuffer_funcs->dirty (if present) to flush buffer changes
+ * for drivers that need it.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect)
+{
+	struct drm_clip_rect clip_rect, *clip = NULL;
+
+	if (!buffer || !buffer->fb || !buffer->fb->funcs->dirty)
+		return 0;
+
+	if (rect) {
+		clip = &clip_rect;
+		clip->x1 = rect->x1;
+		clip->y1 = rect->y1;
+		clip->x2 = rect->x2;
+		clip->y2 = rect->y2;
+	}
+
+	return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
+					0, 0, clip, clip ? 1 : 0);
+}
+EXPORT_SYMBOL(drm_client_framebuffer_flush);
+
 #ifdef CONFIG_DEBUG_FS
 static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
 {
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index bbb5689fa9a8..6ef5364d6dfb 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -156,6 +156,7 @@ struct drm_client_buffer {
 struct drm_client_buffer *
 drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format);
 void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
+int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect);
 void *drm_client_buffer_vmap(struct drm_client_buffer *buffer);
 void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
 
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 05/10] drm/client: Add drm_client_modeset_check()
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

Add a way for client to check the configuration before comitting.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_client_modeset.c | 35 ++++++++++++++++++++++++----
 include/drm/drm_client.h             |  1 +
 2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
index 7443114bd713..177158ff2a40 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -966,7 +966,7 @@ bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
 }
 EXPORT_SYMBOL(drm_client_rotation);
 
-static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active)
+static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
 {
 	struct drm_device *dev = client->dev;
 	struct drm_plane *plane;
@@ -1033,7 +1033,10 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 		}
 	}
 
-	ret = drm_atomic_commit(state);
+	if (check)
+		ret = drm_atomic_check_only(state);
+	else
+		ret = drm_atomic_commit(state);
 
 out_state:
 	if (ret == -EDEADLK)
@@ -1094,6 +1097,30 @@ static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
 	return ret;
 }
 
+/**
+ * drm_client_modeset_check() - Check CRTC configuration
+ * @client: DRM client
+ *
+ * Check modeset configuration.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_check(struct drm_client_dev *client)
+{
+	int ret;
+
+	if (!drm_drv_uses_atomic_modeset(client->dev))
+		return 0;
+
+	mutex_lock(&client->modeset_mutex);
+	ret = drm_client_modeset_commit_atomic(client, true, true);
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_check);
+
 /**
  * drm_client_modeset_commit_locked() - Force commit CRTC configuration
  * @client: DRM client
@@ -1112,7 +1139,7 @@ int drm_client_modeset_commit_locked(struct drm_client_dev *client)
 
 	mutex_lock(&client->modeset_mutex);
 	if (drm_drv_uses_atomic_modeset(dev))
-		ret = drm_client_modeset_commit_atomic(client, true);
+		ret = drm_client_modeset_commit_atomic(client, true, false);
 	else
 		ret = drm_client_modeset_commit_legacy(client);
 	mutex_unlock(&client->modeset_mutex);
@@ -1188,7 +1215,7 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
 
 	mutex_lock(&client->modeset_mutex);
 	if (drm_drv_uses_atomic_modeset(dev))
-		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON);
+		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
 	else
 		drm_client_modeset_dpms_legacy(client, mode);
 	mutex_unlock(&client->modeset_mutex);
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index 6ef5364d6dfb..b6ffa4863e45 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -164,6 +164,7 @@ int drm_client_modeset_create(struct drm_client_dev *client);
 void drm_client_modeset_free(struct drm_client_dev *client);
 int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
 bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
+int drm_client_modeset_check(struct drm_client_dev *client);
 int drm_client_modeset_commit_locked(struct drm_client_dev *client);
 int drm_client_modeset_commit(struct drm_client_dev *client);
 int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
-- 
2.23.0


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

* [PATCH 05/10] drm/client: Add drm_client_modeset_check()
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

Add a way for client to check the configuration before comitting.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_client_modeset.c | 35 ++++++++++++++++++++++++----
 include/drm/drm_client.h             |  1 +
 2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
index 7443114bd713..177158ff2a40 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -966,7 +966,7 @@ bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
 }
 EXPORT_SYMBOL(drm_client_rotation);
 
-static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active)
+static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
 {
 	struct drm_device *dev = client->dev;
 	struct drm_plane *plane;
@@ -1033,7 +1033,10 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 		}
 	}
 
-	ret = drm_atomic_commit(state);
+	if (check)
+		ret = drm_atomic_check_only(state);
+	else
+		ret = drm_atomic_commit(state);
 
 out_state:
 	if (ret == -EDEADLK)
@@ -1094,6 +1097,30 @@ static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
 	return ret;
 }
 
+/**
+ * drm_client_modeset_check() - Check CRTC configuration
+ * @client: DRM client
+ *
+ * Check modeset configuration.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_check(struct drm_client_dev *client)
+{
+	int ret;
+
+	if (!drm_drv_uses_atomic_modeset(client->dev))
+		return 0;
+
+	mutex_lock(&client->modeset_mutex);
+	ret = drm_client_modeset_commit_atomic(client, true, true);
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_check);
+
 /**
  * drm_client_modeset_commit_locked() - Force commit CRTC configuration
  * @client: DRM client
@@ -1112,7 +1139,7 @@ int drm_client_modeset_commit_locked(struct drm_client_dev *client)
 
 	mutex_lock(&client->modeset_mutex);
 	if (drm_drv_uses_atomic_modeset(dev))
-		ret = drm_client_modeset_commit_atomic(client, true);
+		ret = drm_client_modeset_commit_atomic(client, true, false);
 	else
 		ret = drm_client_modeset_commit_legacy(client);
 	mutex_unlock(&client->modeset_mutex);
@@ -1188,7 +1215,7 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
 
 	mutex_lock(&client->modeset_mutex);
 	if (drm_drv_uses_atomic_modeset(dev))
-		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON);
+		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
 	else
 		drm_client_modeset_dpms_legacy(client, mode);
 	mutex_unlock(&client->modeset_mutex);
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index 6ef5364d6dfb..b6ffa4863e45 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -164,6 +164,7 @@ int drm_client_modeset_create(struct drm_client_dev *client);
 void drm_client_modeset_free(struct drm_client_dev *client);
 int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
 bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
+int drm_client_modeset_check(struct drm_client_dev *client);
 int drm_client_modeset_commit_locked(struct drm_client_dev *client);
 int drm_client_modeset_commit(struct drm_client_dev *client);
 int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 06/10] drm/client: Add a way to set modeset, properties and rotation
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

This adds functions for clients that need more control over the
configuration than what's setup by drm_client_modeset_probe().
Connector, fb and display mode can be set using drm_client_modeset_set().
Plane rotation can be set using drm_client_modeset_set_rotation() and
other properties using drm_client_modeset_set_property(). Property
setting is only implemented for atomic drivers.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_client_modeset.c | 139 +++++++++++++++++++++++++++
 include/drm/drm_client.h             |  38 +++++++-
 2 files changed, 176 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
index 177158ff2a40..1eef6869cae1 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -83,6 +83,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client)
 		}
 		modeset->num_connectors = 0;
 	}
+
+	kfree(client->properties);
+	client->properties = NULL;
+	client->num_properties = 0;
 }
 
 void drm_client_modeset_free(struct drm_client_dev *client)
@@ -879,6 +883,132 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
 }
 EXPORT_SYMBOL(drm_client_modeset_probe);
 
+/**
+ * drm_client_modeset_set() - Set modeset configuration
+ * @client: DRM client
+ * @connector: Connector
+ * @mode: Display mode
+ * @fb: Framebuffer
+ *
+ * This function releases any current modeset info, including properties, and
+ * sets the new modeset in the client's modeset array.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
+			   struct drm_display_mode *mode, struct drm_framebuffer *fb)
+{
+	struct drm_mode_set *modeset;
+	int ret = -ENOENT;
+
+	mutex_lock(&client->modeset_mutex);
+
+	drm_client_modeset_release(client);
+
+	if (!connector || !mode || !fb) {
+		ret = 0;
+		goto unlock;
+	}
+
+	drm_client_for_each_modeset(modeset, client) {
+		if (!connector_has_possible_crtc(connector, modeset->crtc))
+			continue;
+
+		modeset->mode = drm_mode_duplicate(client->dev, mode);
+		if (!modeset->mode) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V);
+
+		drm_connector_get(connector);
+		modeset->connectors[modeset->num_connectors++] = connector;
+
+		modeset->fb = fb;
+		ret = 0;
+		break;
+	}
+unlock:
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_set);
+
+/**
+ * drm_client_modeset_set_property() - Set a property on the current configuration
+ * @client: DRM client
+ * @obj: DRM Mode Object
+ * @prop: DRM Property
+ * @value: Property value
+ *
+ * Note: Currently only implemented for atomic drivers.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
+				    struct drm_property *prop, u64 value)
+{
+	struct drm_client_property *properties;
+	int ret = 0;
+
+	if (!prop)
+		return -EINVAL;
+
+	mutex_lock(&client->modeset_mutex);
+
+	properties = krealloc(client->properties,
+			      (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL);
+	if (!properties) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	properties[client->num_properties].obj = obj;
+	properties[client->num_properties].prop = prop;
+	properties[client->num_properties].value = value;
+	client->properties = properties;
+	client->num_properties++;
+unlock:
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_set_property);
+
+/**
+ * drm_client_modeset_set_rotation() - Set rotation on the current configuration
+ * @client: DRM client
+ * @value: Rotation value
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value)
+{
+	struct drm_plane *plane = NULL;
+	struct drm_mode_set *modeset;
+
+	mutex_lock(&client->modeset_mutex);
+	drm_client_for_each_modeset(modeset, client) {
+		if (modeset->mode) {
+			plane = modeset->crtc->primary;
+			break;
+		}
+	}
+	mutex_unlock(&client->modeset_mutex);
+
+	if (!plane)
+		return -ENOENT;
+
+	return drm_client_modeset_set_property(client, &plane->base,
+					       plane->rotation_property, value);
+}
+EXPORT_SYMBOL(drm_client_modeset_set_rotation);
+
 /**
  * drm_client_rotation() - Check the initial rotation value
  * @modeset: DRM modeset
@@ -973,6 +1103,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 	struct drm_atomic_state *state;
 	struct drm_modeset_acquire_ctx ctx;
 	struct drm_mode_set *mode_set;
+	unsigned int i;
 	int ret;
 
 	drm_modeset_acquire_init(&ctx, 0);
@@ -1033,6 +1164,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 		}
 	}
 
+	for (i = 0; i < client->num_properties; i++) {
+		struct drm_client_property *prop = &client->properties[i];
+
+		ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value);
+		if (ret)
+			goto out_state;
+	}
+
 	if (check)
 		ret = drm_atomic_check_only(state);
 	else
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index b6ffa4863e45..4b266741ec0e 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -16,6 +16,7 @@ struct drm_file;
 struct drm_framebuffer;
 struct drm_gem_object;
 struct drm_minor;
+struct drm_property;
 struct module;
 
 /**
@@ -64,6 +65,26 @@ struct drm_client_funcs {
 	int (*hotplug)(struct drm_client_dev *client);
 };
 
+/**
+ * struct drm_client_property - DRM client property
+ */
+struct drm_client_property {
+	/**
+	 * @obj: DRM Mode Object to which the property belongs.
+	 */
+	struct drm_mode_object *obj;
+
+	/**
+	 * @prop: DRM Property.
+	 */
+	struct drm_property *prop;
+
+	/**
+	 * @value: Property value.
+	 */
+	u64 value;
+};
+
 /**
  * struct drm_client_dev - DRM client instance
  */
@@ -97,7 +118,7 @@ struct drm_client_dev {
 	struct drm_file *file;
 
 	/**
-	 * @modeset_mutex: Protects @modesets.
+	 * @modeset_mutex: Protects @modesets and @properties.
 	 */
 	struct mutex modeset_mutex;
 
@@ -105,6 +126,16 @@ struct drm_client_dev {
 	 * @modesets: CRTC configurations
 	 */
 	struct drm_mode_set *modesets;
+
+	/**
+	 * @properties: DRM properties attached to the configuration.
+	 */
+	struct drm_client_property *properties;
+
+	/**
+	 * @num_properties: Number of attached properties.
+	 */
+	unsigned int num_properties;
 };
 
 int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
@@ -163,6 +194,11 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
 int drm_client_modeset_create(struct drm_client_dev *client);
 void drm_client_modeset_free(struct drm_client_dev *client);
 int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
+int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
+			   struct drm_display_mode *mode, struct drm_framebuffer *fb);
+int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
+				    struct drm_property *prop, u64 value);
+int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value);
 bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
 int drm_client_modeset_check(struct drm_client_dev *client);
 int drm_client_modeset_commit_locked(struct drm_client_dev *client);
-- 
2.23.0


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

* [PATCH 06/10] drm/client: Add a way to set modeset, properties and rotation
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

This adds functions for clients that need more control over the
configuration than what's setup by drm_client_modeset_probe().
Connector, fb and display mode can be set using drm_client_modeset_set().
Plane rotation can be set using drm_client_modeset_set_rotation() and
other properties using drm_client_modeset_set_property(). Property
setting is only implemented for atomic drivers.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_client_modeset.c | 139 +++++++++++++++++++++++++++
 include/drm/drm_client.h             |  38 +++++++-
 2 files changed, 176 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
index 177158ff2a40..1eef6869cae1 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -83,6 +83,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client)
 		}
 		modeset->num_connectors = 0;
 	}
+
+	kfree(client->properties);
+	client->properties = NULL;
+	client->num_properties = 0;
 }
 
 void drm_client_modeset_free(struct drm_client_dev *client)
@@ -879,6 +883,132 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
 }
 EXPORT_SYMBOL(drm_client_modeset_probe);
 
+/**
+ * drm_client_modeset_set() - Set modeset configuration
+ * @client: DRM client
+ * @connector: Connector
+ * @mode: Display mode
+ * @fb: Framebuffer
+ *
+ * This function releases any current modeset info, including properties, and
+ * sets the new modeset in the client's modeset array.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
+			   struct drm_display_mode *mode, struct drm_framebuffer *fb)
+{
+	struct drm_mode_set *modeset;
+	int ret = -ENOENT;
+
+	mutex_lock(&client->modeset_mutex);
+
+	drm_client_modeset_release(client);
+
+	if (!connector || !mode || !fb) {
+		ret = 0;
+		goto unlock;
+	}
+
+	drm_client_for_each_modeset(modeset, client) {
+		if (!connector_has_possible_crtc(connector, modeset->crtc))
+			continue;
+
+		modeset->mode = drm_mode_duplicate(client->dev, mode);
+		if (!modeset->mode) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V);
+
+		drm_connector_get(connector);
+		modeset->connectors[modeset->num_connectors++] = connector;
+
+		modeset->fb = fb;
+		ret = 0;
+		break;
+	}
+unlock:
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_set);
+
+/**
+ * drm_client_modeset_set_property() - Set a property on the current configuration
+ * @client: DRM client
+ * @obj: DRM Mode Object
+ * @prop: DRM Property
+ * @value: Property value
+ *
+ * Note: Currently only implemented for atomic drivers.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
+				    struct drm_property *prop, u64 value)
+{
+	struct drm_client_property *properties;
+	int ret = 0;
+
+	if (!prop)
+		return -EINVAL;
+
+	mutex_lock(&client->modeset_mutex);
+
+	properties = krealloc(client->properties,
+			      (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL);
+	if (!properties) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	properties[client->num_properties].obj = obj;
+	properties[client->num_properties].prop = prop;
+	properties[client->num_properties].value = value;
+	client->properties = properties;
+	client->num_properties++;
+unlock:
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_set_property);
+
+/**
+ * drm_client_modeset_set_rotation() - Set rotation on the current configuration
+ * @client: DRM client
+ * @value: Rotation value
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value)
+{
+	struct drm_plane *plane = NULL;
+	struct drm_mode_set *modeset;
+
+	mutex_lock(&client->modeset_mutex);
+	drm_client_for_each_modeset(modeset, client) {
+		if (modeset->mode) {
+			plane = modeset->crtc->primary;
+			break;
+		}
+	}
+	mutex_unlock(&client->modeset_mutex);
+
+	if (!plane)
+		return -ENOENT;
+
+	return drm_client_modeset_set_property(client, &plane->base,
+					       plane->rotation_property, value);
+}
+EXPORT_SYMBOL(drm_client_modeset_set_rotation);
+
 /**
  * drm_client_rotation() - Check the initial rotation value
  * @modeset: DRM modeset
@@ -973,6 +1103,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 	struct drm_atomic_state *state;
 	struct drm_modeset_acquire_ctx ctx;
 	struct drm_mode_set *mode_set;
+	unsigned int i;
 	int ret;
 
 	drm_modeset_acquire_init(&ctx, 0);
@@ -1033,6 +1164,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 		}
 	}
 
+	for (i = 0; i < client->num_properties; i++) {
+		struct drm_client_property *prop = &client->properties[i];
+
+		ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value);
+		if (ret)
+			goto out_state;
+	}
+
 	if (check)
 		ret = drm_atomic_check_only(state);
 	else
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index b6ffa4863e45..4b266741ec0e 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -16,6 +16,7 @@ struct drm_file;
 struct drm_framebuffer;
 struct drm_gem_object;
 struct drm_minor;
+struct drm_property;
 struct module;
 
 /**
@@ -64,6 +65,26 @@ struct drm_client_funcs {
 	int (*hotplug)(struct drm_client_dev *client);
 };
 
+/**
+ * struct drm_client_property - DRM client property
+ */
+struct drm_client_property {
+	/**
+	 * @obj: DRM Mode Object to which the property belongs.
+	 */
+	struct drm_mode_object *obj;
+
+	/**
+	 * @prop: DRM Property.
+	 */
+	struct drm_property *prop;
+
+	/**
+	 * @value: Property value.
+	 */
+	u64 value;
+};
+
 /**
  * struct drm_client_dev - DRM client instance
  */
@@ -97,7 +118,7 @@ struct drm_client_dev {
 	struct drm_file *file;
 
 	/**
-	 * @modeset_mutex: Protects @modesets.
+	 * @modeset_mutex: Protects @modesets and @properties.
 	 */
 	struct mutex modeset_mutex;
 
@@ -105,6 +126,16 @@ struct drm_client_dev {
 	 * @modesets: CRTC configurations
 	 */
 	struct drm_mode_set *modesets;
+
+	/**
+	 * @properties: DRM properties attached to the configuration.
+	 */
+	struct drm_client_property *properties;
+
+	/**
+	 * @num_properties: Number of attached properties.
+	 */
+	unsigned int num_properties;
 };
 
 int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
@@ -163,6 +194,11 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
 int drm_client_modeset_create(struct drm_client_dev *client);
 void drm_client_modeset_free(struct drm_client_dev *client);
 int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
+int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
+			   struct drm_display_mode *mode, struct drm_framebuffer *fb);
+int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
+				    struct drm_property *prop, u64 value);
+int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value);
 bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
 int drm_client_modeset_check(struct drm_client_dev *client);
 int drm_client_modeset_commit_locked(struct drm_client_dev *client);
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 07/10] drm/format-helper: Add drm_fb_swab()
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

This replaces drm_fb_swab16() with drm_fb_swab() supporting 16 and 32-bit.
Also make pixel line caching optional.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_format_helper.c | 61 +++++++++++++++++++----------
 drivers/gpu/drm/drm_mipi_dbi.c      |  2 +-
 include/drm/drm_format_helper.h     |  4 +-
 3 files changed, 44 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
index 0897cb9aeaff..5c147c49668c 100644
--- a/drivers/gpu/drm/drm_format_helper.c
+++ b/drivers/gpu/drm/drm_format_helper.c
@@ -79,39 +79,60 @@ void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
 EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
 
 /**
- * drm_fb_swab16 - Swap bytes into clip buffer
- * @dst: RGB565 destination buffer
- * @vaddr: RGB565 source buffer
+ * drm_fb_swab - Swap bytes into clip buffer
+ * @dst: Destination buffer
+ * @src: Source buffer
  * @fb: DRM framebuffer
  * @clip: Clip rectangle area to copy
+ * @cached: Source buffer is mapped cached (eg. not write-combined)
+ *
+ * If @cached is false a temporary buffer is used to cache one pixel line at a
+ * time to speed up slow uncached reads.
+ *
+ * This function does not apply clipping on dst, i.e. the destination
+ * is a small buffer containing the clip rect only.
  */
-void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
-		   struct drm_rect *clip)
+void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
+		 struct drm_rect *clip, bool cached)
 {
-	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
+	u8 cpp = fb->format->cpp[0];
+	size_t len = drm_rect_width(clip) * cpp;
+	u16 *src16, *dst16 = dst;
+	u32 *src32, *dst32 = dst;
 	unsigned int x, y;
-	u16 *src, *buf;
+	void *buf = NULL;
 
-	/*
-	 * The cma memory is write-combined so reads are uncached.
-	 * Speed up by fetching one line at a time.
-	 */
-	buf = kmalloc(len, GFP_KERNEL);
-	if (!buf)
+	if (WARN_ON_ONCE(cpp == 1))
 		return;
 
+	if (!cached)
+		buf = kmalloc(len, GFP_KERNEL);
+
+	src += clip_offset(clip, fb->pitches[0], cpp);
+
 	for (y = clip->y1; y < clip->y2; y++) {
-		src = vaddr + (y * fb->pitches[0]);
-		src += clip->x1;
-		memcpy(buf, src, len);
-		src = buf;
-		for (x = clip->x1; x < clip->x2; x++)
-			*dst++ = swab16(*src++);
+		if (buf) {
+			memcpy(buf, src, len);
+			src16 = buf;
+			src32 = buf;
+		} else {
+			src16 = src;
+			src32 = src;
+		}
+
+		for (x = clip->x1; x < clip->x2; x++) {
+			if (cpp == 4)
+				*dst32++ = swab32(*src32++);
+			else
+				*dst16++ = swab16(*src16++);
+		}
+
+		src += fb->pitches[0];
 	}
 
 	kfree(buf);
 }
-EXPORT_SYMBOL(drm_fb_swab16);
+EXPORT_SYMBOL(drm_fb_swab);
 
 static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
 					   unsigned int pixels,
diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c
index 16bff1be4b8a..bfefbcb69287 100644
--- a/drivers/gpu/drm/drm_mipi_dbi.c
+++ b/drivers/gpu/drm/drm_mipi_dbi.c
@@ -217,7 +217,7 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
 	switch (fb->format->format) {
 	case DRM_FORMAT_RGB565:
 		if (swap)
-			drm_fb_swab16(dst, src, fb, clip);
+			drm_fb_swab(dst, src, fb, clip, !import_attach);
 		else
 			drm_fb_memcpy(dst, src, fb, clip);
 		break;
diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h
index ac220aa1a245..5f9e37032468 100644
--- a/include/drm/drm_format_helper.h
+++ b/include/drm/drm_format_helper.h
@@ -14,8 +14,8 @@ void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
 void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
 			   struct drm_framebuffer *fb,
 			   struct drm_rect *clip);
-void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
-		   struct drm_rect *clip);
+void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
+		 struct drm_rect *clip, bool cached);
 void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
 			       struct drm_framebuffer *fb,
 			       struct drm_rect *clip, bool swab);
-- 
2.23.0


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

* [PATCH 07/10] drm/format-helper: Add drm_fb_swab()
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

This replaces drm_fb_swab16() with drm_fb_swab() supporting 16 and 32-bit.
Also make pixel line caching optional.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_format_helper.c | 61 +++++++++++++++++++----------
 drivers/gpu/drm/drm_mipi_dbi.c      |  2 +-
 include/drm/drm_format_helper.h     |  4 +-
 3 files changed, 44 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
index 0897cb9aeaff..5c147c49668c 100644
--- a/drivers/gpu/drm/drm_format_helper.c
+++ b/drivers/gpu/drm/drm_format_helper.c
@@ -79,39 +79,60 @@ void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
 EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
 
 /**
- * drm_fb_swab16 - Swap bytes into clip buffer
- * @dst: RGB565 destination buffer
- * @vaddr: RGB565 source buffer
+ * drm_fb_swab - Swap bytes into clip buffer
+ * @dst: Destination buffer
+ * @src: Source buffer
  * @fb: DRM framebuffer
  * @clip: Clip rectangle area to copy
+ * @cached: Source buffer is mapped cached (eg. not write-combined)
+ *
+ * If @cached is false a temporary buffer is used to cache one pixel line at a
+ * time to speed up slow uncached reads.
+ *
+ * This function does not apply clipping on dst, i.e. the destination
+ * is a small buffer containing the clip rect only.
  */
-void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
-		   struct drm_rect *clip)
+void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
+		 struct drm_rect *clip, bool cached)
 {
-	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
+	u8 cpp = fb->format->cpp[0];
+	size_t len = drm_rect_width(clip) * cpp;
+	u16 *src16, *dst16 = dst;
+	u32 *src32, *dst32 = dst;
 	unsigned int x, y;
-	u16 *src, *buf;
+	void *buf = NULL;
 
-	/*
-	 * The cma memory is write-combined so reads are uncached.
-	 * Speed up by fetching one line at a time.
-	 */
-	buf = kmalloc(len, GFP_KERNEL);
-	if (!buf)
+	if (WARN_ON_ONCE(cpp == 1))
 		return;
 
+	if (!cached)
+		buf = kmalloc(len, GFP_KERNEL);
+
+	src += clip_offset(clip, fb->pitches[0], cpp);
+
 	for (y = clip->y1; y < clip->y2; y++) {
-		src = vaddr + (y * fb->pitches[0]);
-		src += clip->x1;
-		memcpy(buf, src, len);
-		src = buf;
-		for (x = clip->x1; x < clip->x2; x++)
-			*dst++ = swab16(*src++);
+		if (buf) {
+			memcpy(buf, src, len);
+			src16 = buf;
+			src32 = buf;
+		} else {
+			src16 = src;
+			src32 = src;
+		}
+
+		for (x = clip->x1; x < clip->x2; x++) {
+			if (cpp == 4)
+				*dst32++ = swab32(*src32++);
+			else
+				*dst16++ = swab16(*src16++);
+		}
+
+		src += fb->pitches[0];
 	}
 
 	kfree(buf);
 }
-EXPORT_SYMBOL(drm_fb_swab16);
+EXPORT_SYMBOL(drm_fb_swab);
 
 static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
 					   unsigned int pixels,
diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c
index 16bff1be4b8a..bfefbcb69287 100644
--- a/drivers/gpu/drm/drm_mipi_dbi.c
+++ b/drivers/gpu/drm/drm_mipi_dbi.c
@@ -217,7 +217,7 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
 	switch (fb->format->format) {
 	case DRM_FORMAT_RGB565:
 		if (swap)
-			drm_fb_swab16(dst, src, fb, clip);
+			drm_fb_swab(dst, src, fb, clip, !import_attach);
 		else
 			drm_fb_memcpy(dst, src, fb, clip);
 		break;
diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h
index ac220aa1a245..5f9e37032468 100644
--- a/include/drm/drm_format_helper.h
+++ b/include/drm/drm_format_helper.h
@@ -14,8 +14,8 @@ void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
 void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
 			   struct drm_framebuffer *fb,
 			   struct drm_rect *clip);
-void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
-		   struct drm_rect *clip);
+void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
+		 struct drm_rect *clip, bool cached);
 void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
 			       struct drm_framebuffer *fb,
 			       struct drm_rect *clip, bool swab);
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 08/10] drm: Add Generic USB Display driver
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

This adds a generic USB display driver with the intention that it can be
used with future USB interfaced low end displays/adapters. The Linux
gadget device driver will serve as the canonical device implementation.

The following DRM properties are supported:
- Plane rotation
- Connector TV properties

There is also support for backlight brightness exposed as a backlight
device.

Display modes can be made available to the host driver either as DRM
display modes or through EDID. If both are present, EDID is just passed
on to userspace.

Performance is preferred over color depth, so if the device supports
RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16.

If the device transfer buffer can't fit an uncompressed framebuffer
update, the update is split up into parts that do fit.

Optimal user experience is achieved by providing damage reports either by
setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB.

LZ4 compression is used if the device supports it.

The driver supports a one bit monochrome transfer format: R1. This is not
implemented in the gadget driver. It is added in preparation for future
monochrome e-ink displays.

The driver is MIT licensed to smooth the path for any BSD port of the
driver.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 MAINTAINERS                             |   8 +
 drivers/gpu/drm/Kconfig                 |   2 +
 drivers/gpu/drm/Makefile                |   1 +
 drivers/gpu/drm/gud/Kconfig             |  14 +
 drivers/gpu/drm/gud/Makefile            |   4 +
 drivers/gpu/drm/gud/gud_drm_connector.c | 629 ++++++++++++++++++++++++
 drivers/gpu/drm/gud/gud_drm_drv.c       | 624 +++++++++++++++++++++++
 drivers/gpu/drm/gud/gud_drm_internal.h  |  66 +++
 drivers/gpu/drm/gud/gud_drm_pipe.c      | 423 ++++++++++++++++
 include/drm/gud_drm.h                   | 356 ++++++++++++++
 10 files changed, 2127 insertions(+)
 create mode 100644 drivers/gpu/drm/gud/Kconfig
 create mode 100644 drivers/gpu/drm/gud/Makefile
 create mode 100644 drivers/gpu/drm/gud/gud_drm_connector.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_drv.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_internal.h
 create mode 100644 drivers/gpu/drm/gud/gud_drm_pipe.c
 create mode 100644 include/drm/gud_drm.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 08ae8ea76939..bb4609984e9a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5282,6 +5282,14 @@ S:	Maintained
 F:	drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c
 F:	Documentation/devicetree/bindings/display/panel/feiyang,fy07024di26a30d.txt
 
+DRM DRIVER FOR GENERIC USB DISPLAY
+M:	Noralf Trønnes <noralf@tronnes.org>
+S:	Maintained
+W:	https://github.com/notro/gud/wiki
+T:	git git://anongit.freedesktop.org/drm/drm-misc
+F:	drivers/gpu/drm/gud/
+F:	include/drm/gud_drm.h
+
 DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
 M:	Hans de Goede <hdegoede@redhat.com>
 T:	git git://anongit.freedesktop.org/drm/drm-misc
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index f6e13e18c9ca..928bbf5cdc10 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -396,6 +396,8 @@ source "drivers/gpu/drm/aspeed/Kconfig"
 
 source "drivers/gpu/drm/mcde/Kconfig"
 
+source "drivers/gpu/drm/gud/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0d17662dde0a..86af6c2da40d 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -123,3 +123,4 @@ obj-$(CONFIG_DRM_LIMA)  += lima/
 obj-$(CONFIG_DRM_PANFROST) += panfrost/
 obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
 obj-$(CONFIG_DRM_MCDE) += mcde/
+obj-y			+= gud/
diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig
new file mode 100644
index 000000000000..203d4490f1c7
--- /dev/null
+++ b/drivers/gpu/drm/gud/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config DRM_GUD
+	tristate "Generic USB Display"
+	depends on DRM && USB
+	select LZ4_COMPRESS
+	select DRM_KMS_HELPER
+	select DRM_GEM_SHMEM_HELPER
+	select DRM_BACKLIGHT_HELPER
+	help
+	  This is a DRM display driver for Generic USB Displays or display
+	  adapters.
+
+	  If M is selected the module will be called gud_drm.
diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile
new file mode 100644
index 000000000000..73ed7ef3da94
--- /dev/null
+++ b/drivers/gpu/drm/gud/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+gud_drm-objs			:= gud_drm_drv.o gud_drm_pipe.o gud_drm_connector.o
+obj-$(CONFIG_DRM_GUD)		+= gud_drm.o
diff --git a/drivers/gpu/drm/gud/gud_drm_connector.c b/drivers/gpu/drm/gud/gud_drm_connector.c
new file mode 100644
index 000000000000..d1042b563a98
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_connector.c
@@ -0,0 +1,629 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_backlight_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+struct gud_drm_connector {
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+
+	u32 flags;
+
+	/* Supported properties */
+	u16 *properties;
+	unsigned int num_properties;
+
+	/* Initial gadget tv state if applicable, applied on state reset */
+	struct drm_tv_connector_state initial_tv_state;
+
+	/*
+	 * Initial gadget backlight brightness if applicable, applied on state reset.
+	 * The value -ENODEV is used internally to signal no backlight.
+	 */
+	int initial_brightness;
+
+	/* Supported display modes in transfer format */
+	struct gud_drm_display_mode *modes;
+	unsigned int num_modes;
+
+	/* EDID */
+	void *edid;
+	size_t edid_len;
+};
+
+static inline struct gud_drm_connector *to_gud_drm_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct gud_drm_connector, connector);
+}
+
+static void gud_drm_connector_modes_clear(struct gud_drm_connector *gconn)
+{
+	kfree(gconn->modes);
+	gconn->modes = NULL;
+	gconn->num_modes = 0;
+}
+
+static int gud_drm_connector_modes_get(struct gud_drm_connector *gconn, unsigned int num_modes)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(gconn->connector.dev);
+	unsigned int index = gconn->connector.index;
+	int ret = 0;
+
+	if (!num_modes)
+		goto clear;
+
+	gud_drm_connector_modes_clear(gconn);
+
+	gconn->modes = kmalloc_array(num_modes, sizeof(*gconn->modes), GFP_KERNEL);
+	if (!gconn->modes)
+		return -ENOMEM;
+
+	gconn->num_modes = num_modes;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_MODES, index,
+			      gconn->modes, num_modes * sizeof(*gconn->modes));
+	if (ret)
+		goto clear;
+
+	return 0;
+clear:
+	gud_drm_connector_modes_clear(gconn);
+
+	return ret;
+}
+
+static void gud_drm_connector_edid_clear(struct gud_drm_connector *gconn)
+{
+	kfree(gconn->edid);
+	gconn->edid = NULL;
+	gconn->edid_len = 0;
+}
+
+static int gud_drm_connector_edid_get(struct gud_drm_connector *gconn, size_t len)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(gconn->connector.dev);
+	unsigned int index = gconn->connector.index;
+	int ret = 0;
+
+	if (!len)
+		goto clear;
+
+	gud_drm_connector_edid_clear(gconn);
+
+	gconn->edid = kmalloc(len, GFP_KERNEL);
+	if (!gconn->edid)
+		return -ENOMEM;
+
+	gconn->edid_len = len;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_EDID, index, gconn->edid, len);
+	if (ret)
+		goto clear;
+
+	return 0;
+clear:
+	gud_drm_connector_edid_clear(gconn);
+
+	return ret;
+}
+
+static int gud_drm_connector_detect_safe(struct drm_connector *connector, bool force)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+	struct gud_drm_device *gdrm = to_gud_drm_device(connector->dev);
+	struct gud_drm_req_get_connector_status req;
+	u16 num_modes, edid_len;
+	int status, ret;
+	bool changed;
+
+	if (force) {
+		ret = gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_CONNECTOR_FORCE_DETECT,
+				      connector->index, NULL, 0);
+		if (ret)
+			goto free;
+	}
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_STATUS,
+			      connector->index, &req, sizeof(req));
+	if (ret)
+		goto free;
+
+	changed = req.status & GUD_DRM_CONNECTOR_STATUS_CHANGED;
+	status = req.status & GUD_DRM_CONNECTOR_STATUS_MASK;
+	if (status > connector_status_unknown)
+		status = connector_status_unknown;
+	num_modes = le16_to_cpu(req.num_modes);
+	edid_len = le16_to_cpu(req.edid_len);
+
+	if (!num_modes && !edid_len) {
+		ret = connector_status_disconnected;
+		goto free;
+	}
+
+	if (!changed && connector->status == status &&
+	    gconn->num_modes == num_modes && gconn->edid_len == edid_len)
+		return status;
+
+	ret = gud_drm_connector_modes_get(gconn, num_modes);
+	if (ret)
+		goto free;
+
+	ret = gud_drm_connector_edid_get(gconn, edid_len);
+	if (ret)
+		goto free;
+
+	return status;
+free:
+	gud_drm_connector_modes_clear(gconn);
+	gud_drm_connector_edid_clear(gconn);
+
+	return ret < 0 ? connector_status_unknown : ret;
+}
+
+static int gud_drm_connector_detect(struct drm_connector *connector,
+				    struct drm_modeset_acquire_ctx *ctx, bool force)
+{
+	int idx, ret;
+
+	if (!drm_dev_enter(connector->dev, &idx))
+		return -ENODEV;
+
+	ret = gud_drm_connector_detect_safe(connector, force);
+
+	drm_dev_exit(idx);
+
+	return ret;
+}
+
+static int gud_drm_connector_get_modes(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+	unsigned int i, num_modes = 0;
+
+	if (!gconn->num_modes)
+		return drm_add_edid_modes(connector, gconn->edid);
+
+	for (i = 0; i < gconn->num_modes; i++) {
+		struct drm_display_mode *mode;
+
+		mode = drm_mode_create(connector->dev);
+		if (!mode)
+			goto out;
+
+		gud_drm_to_display_mode(mode, &gconn->modes[i]);
+
+		drm_mode_probed_add(connector, mode);
+		num_modes++;
+	}
+out:
+	if (gconn->edid_len)
+		drm_connector_update_edid_property(connector, gconn->edid);
+
+	return num_modes;
+}
+
+static int gud_drm_connector_atomic_check(struct drm_connector *connector,
+					  struct drm_atomic_state *state)
+{
+	struct drm_connector_state *new_state;
+	struct drm_crtc_state *new_crtc_state;
+	struct drm_connector_state *old_state;
+
+	new_state = drm_atomic_get_new_connector_state(state, connector);
+	if (!new_state->crtc)
+		return 0;
+
+	old_state = drm_atomic_get_old_connector_state(state, connector);
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
+
+	if (old_state->backlight_brightness != new_state->backlight_brightness ||
+	    old_state->tv.subconnector != new_state->tv.subconnector ||
+	    old_state->tv.margins.left != new_state->tv.margins.left ||
+	    old_state->tv.margins.right != new_state->tv.margins.right ||
+	    old_state->tv.margins.top != new_state->tv.margins.top ||
+	    old_state->tv.margins.bottom != new_state->tv.margins.bottom ||
+	    old_state->tv.mode != new_state->tv.mode ||
+	    old_state->tv.brightness != new_state->tv.brightness ||
+	    old_state->tv.contrast != new_state->tv.contrast ||
+	    old_state->tv.flicker_reduction != new_state->tv.flicker_reduction ||
+	    old_state->tv.overscan != new_state->tv.overscan ||
+	    old_state->tv.saturation != new_state->tv.saturation ||
+	    old_state->tv.hue != new_state->tv.hue)
+		new_crtc_state->connectors_changed = true;
+
+	return 0;
+}
+
+static const struct drm_connector_helper_funcs gud_drm_connector_helper_funcs = {
+	.detect_ctx = gud_drm_connector_detect,
+	.get_modes = gud_drm_connector_get_modes,
+	.atomic_check = gud_drm_connector_atomic_check,
+};
+
+static int gud_drm_connector_late_register(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+
+	if (gconn->initial_brightness < 0)
+		return 0;
+
+	return drm_backlight_register(connector);
+}
+
+static void gud_drm_connector_destroy(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+
+	drm_connector_cleanup(connector);
+	gud_drm_connector_modes_clear(gconn);
+	gud_drm_connector_edid_clear(gconn);
+	kfree(gconn->properties);
+	kfree(gconn);
+}
+
+static void gud_drm_connector_reset(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+
+	drm_atomic_helper_connector_reset(connector);
+	connector->state->tv = gconn->initial_tv_state;
+	/* Set margins from command line */
+	drm_atomic_helper_connector_tv_reset(connector);
+	if (gconn->initial_brightness >= 0)
+		connector->state->backlight_brightness = gconn->initial_brightness;
+}
+
+static const struct drm_connector_funcs gud_drm_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.late_register = gud_drm_connector_late_register,
+	.early_unregister = drm_backlight_unregister,
+	.destroy = gud_drm_connector_destroy,
+	.reset = gud_drm_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/*
+ * The tv.mode property is shared among the connectors and its enum names are
+ * driver specific. This means that if more than one connector uses tv.mode,
+ * the enum names has to be the same.
+ */
+static int gud_drm_connector_add_tv_mode(struct gud_drm_device *gdrm,
+					 struct drm_connector *connector, u64 val)
+{
+	unsigned int i, num_modes;
+	const char **modes;
+	size_t buf_len;
+	char *buf;
+	int ret;
+
+	num_modes = val >> GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT;
+
+	if (!num_modes)
+		return -EINVAL;
+
+	buf_len = num_modes * DRM_PROP_NAME_LEN;
+	modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL);
+	buf = kmalloc(buf_len, GFP_KERNEL);
+	if (!modes || !buf) {
+		ret = -ENOMEM;
+		goto free;
+	}
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_TV_MODE_VALUES,
+			      connector->index, buf, buf_len);
+	if (ret)
+		goto free;
+
+	for (i = 0; i < num_modes; i++)
+		modes[i] = &buf[i * DRM_PROP_NAME_LEN];
+
+	ret = drm_mode_create_tv_properties(connector->dev, num_modes, modes);
+free:
+	kfree(modes);
+	kfree(buf);
+
+	return ret;
+}
+
+static struct drm_property *
+gud_drm_connector_property_lookup(struct drm_connector *connector, u16 prop)
+{
+	struct drm_mode_config *config = &connector->dev->mode_config;
+
+	switch (prop) {
+	case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+		return config->tv_select_subconnector_property;
+	case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+		return config->tv_left_margin_property;
+	case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+		return config->tv_right_margin_property;
+	case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+		return config->tv_top_margin_property;
+	case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+		return config->tv_bottom_margin_property;
+	case GUD_DRM_PROPERTY_TV_MODE:
+		return config->tv_mode_property;
+	case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+		return config->tv_brightness_property;
+	case GUD_DRM_PROPERTY_TV_CONTRAST:
+		return config->tv_contrast_property;
+	case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+		return config->tv_flicker_reduction_property;
+	case GUD_DRM_PROPERTY_TV_OVERSCAN:
+		return config->tv_overscan_property;
+	case GUD_DRM_PROPERTY_TV_SATURATION:
+		return config->tv_saturation_property;
+	case GUD_DRM_PROPERTY_TV_HUE:
+		return config->tv_hue_property;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+}
+
+static unsigned int *gud_drm_connector_tv_state_val(u16 prop, struct drm_tv_connector_state *state)
+{
+	switch (prop) {
+	case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+		return &state->subconnector;
+	case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+		return &state->margins.left;
+	case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+		return &state->margins.right;
+	case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+		return &state->margins.top;
+	case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+		return &state->margins.bottom;
+	case GUD_DRM_PROPERTY_TV_MODE:
+		return &state->mode;
+	case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+		return &state->brightness;
+	case GUD_DRM_PROPERTY_TV_CONTRAST:
+		return &state->contrast;
+	case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+		return &state->flicker_reduction;
+	case GUD_DRM_PROPERTY_TV_OVERSCAN:
+		return &state->overscan;
+	case GUD_DRM_PROPERTY_TV_SATURATION:
+		return &state->saturation;
+	case GUD_DRM_PROPERTY_TV_HUE:
+		return &state->hue;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+}
+
+static int gud_drm_connector_add_properties(struct gud_drm_device *gdrm,
+					    struct gud_drm_connector *gconn,
+					    unsigned int num_properties)
+{
+	struct drm_device *drm = &gdrm->drm;
+	struct drm_connector *connector = &gconn->connector;
+	struct gud_drm_property *properties;
+	bool need_tv_props = false;
+	unsigned int i;
+	int ret;
+
+	gconn->properties = kcalloc(num_properties, sizeof(*gconn->properties), GFP_KERNEL);
+	if (!gconn->properties)
+		return -ENOMEM;
+
+	properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL);
+	if (!properties)
+		return -ENOMEM;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_PROPERTIES, connector->index,
+			      properties, num_properties * sizeof(*properties));
+	if (ret)
+		goto out;
+
+	for (i = 0; i < num_properties; i++) {
+		u16 prop = le16_to_cpu(properties[i].prop);
+		u64 val = le64_to_cpu(properties[i].val);
+
+		drm_dbg(drm, "property: %u = %llu(0x%llx)\n", prop, val, val);
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+			ret = drm_mode_create_tv_margin_properties(drm);
+			if (ret)
+				goto out;
+			break;
+		case GUD_DRM_PROPERTY_TV_MODE:
+			ret = gud_drm_connector_add_tv_mode(gdrm, connector, val);
+			if (ret)
+				goto out;
+			break;
+		case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_CONTRAST:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_OVERSCAN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_SATURATION:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_HUE:
+			need_tv_props = true;
+			break;
+		case GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS:
+			if (val > 100) {
+				ret = -EINVAL;
+				goto out;
+			}
+			gconn->initial_brightness = val;
+			break;
+		default:
+			/* New ones might show up in future devices, skip those we don't know. */
+			drm_dbg(drm, "Unknown property: %u\n", prop);
+			continue;
+		}
+
+		gconn->properties[gconn->num_properties++] = prop;
+	}
+
+	if (!gconn->num_properties)
+		goto out;
+
+	if (need_tv_props) {
+		/* This is a no-op if already added. */
+		ret = drm_mode_create_tv_properties(drm, 0, NULL);
+		if (ret)
+			goto out;
+	}
+
+	for (i = 0; i < num_properties; i++) {
+		u16 prop = le16_to_cpu(properties[i].prop);
+		u64 val = le64_to_cpu(properties[i].val);
+		struct drm_property *property;
+		unsigned int *state_val;
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS:
+			/* not a DRM property */
+			continue;
+		case GUD_DRM_PROPERTY_TV_MODE:
+			val = val & (BIT(GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT) - 1);
+			break;
+		}
+
+		property = gud_drm_connector_property_lookup(connector, prop);
+		if (IS_ERR(property))
+			continue;
+
+		state_val = gud_drm_connector_tv_state_val(prop, &gconn->initial_tv_state);
+		if (IS_ERR(state_val))
+			continue;
+
+		*state_val = val;
+		drm_object_attach_property(&connector->base, property, 0);
+	}
+out:
+	kfree(properties);
+
+	return ret;
+}
+
+int gud_drm_connector_fill_properties(struct drm_connector *connector,
+				      struct drm_connector_state *connector_state,
+				      struct gud_drm_property *properties)
+{
+	struct gud_drm_connector *gconn;
+	unsigned int i;
+
+	gconn = to_gud_drm_connector(connector);
+
+	/* Only interested in the count? */
+	if (!connector_state)
+		return gconn->num_properties;
+
+	for (i = 0; i < gconn->num_properties; i++) {
+		u16 prop = gconn->properties[i];
+		u64 val;
+
+		if (prop == GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS) {
+			val = connector_state->backlight_brightness;
+		} else {
+			unsigned int *state_val;
+
+			state_val = gud_drm_connector_tv_state_val(prop, &connector_state->tv);
+			if (WARN_ON_ONCE(IS_ERR(state_val)))
+				return PTR_ERR(state_val);
+
+			val = *state_val;
+		}
+
+		properties[i].prop = cpu_to_le16(prop);
+		properties[i].val = cpu_to_le64(val);
+	}
+
+	return gconn->num_properties;
+}
+
+int gud_drm_connector_create(struct gud_drm_device *gdrm, unsigned int index)
+{
+	struct gud_drm_req_get_connector desc;
+	struct drm_device *drm = &gdrm->drm;
+	struct gud_drm_connector *gconn;
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+	int ret;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR, index, &desc, sizeof(desc));
+	if (ret)
+		return ret;
+
+	drm_dbg(drm, "index=%u type=%u flags=0x%x num_properties=%u\n", index,
+		desc.connector_type, le32_to_cpu(desc.flags), desc.num_properties);
+
+	/* REVISIT: This needs to be updated as new types are added */
+	if (desc.connector_type > DRM_MODE_CONNECTOR_SPI)
+		return -EINVAL;
+
+	gconn = kzalloc(sizeof(*gconn), GFP_KERNEL);
+	if (!gconn)
+		return -ENOMEM;
+
+	gconn->initial_brightness = -ENODEV;
+	gconn->flags = le32_to_cpu(desc.flags);
+	connector = &gconn->connector;
+
+	drm_connector_helper_add(connector, &gud_drm_connector_helper_funcs);
+	ret = drm_connector_init(drm, connector, &gud_drm_connector_funcs, desc.connector_type);
+	if (ret) {
+		kfree(connector);
+		return ret;
+	}
+
+	if (WARN_ON(connector->index != index))
+		return -EINVAL;
+
+	if (gconn->flags & GUD_DRM_CONNECTOR_FLAGS_POLL)
+		connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT);
+
+	if (desc.num_properties) {
+		ret = gud_drm_connector_add_properties(gdrm, gconn, desc.num_properties);
+		if (ret) {
+			dev_err(drm->dev, "Failed to add connector/%u properties\n", index);
+			return ret;
+		}
+	}
+
+	/* The first connector is attached to the existing simple pipe encoder */
+	if (!connector->index) {
+		encoder = &gdrm->pipe.encoder;
+	} else {
+		encoder = &gconn->encoder;
+
+		ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
+		if (ret)
+			return ret;
+
+		encoder->possible_crtcs = 1;
+	}
+
+	return drm_connector_attach_encoder(connector, encoder);
+}
diff --git a/drivers/gpu/drm/gud/gud_drm_drv.c b/drivers/gpu/drm/gud/gud_drm_drv.c
new file mode 100644
index 000000000000..4e1a0115ca6c
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_drv.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/lz4.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/string_helpers.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+/* Only used internally */
+static const struct drm_format_info gud_drm_format_r1 = {
+	.format = GUD_DRM_FORMAT_R1,
+	.num_planes = 1,
+	.char_per_block = { 1, 0, 0 },
+	.block_w = { 8, 0, 0 },
+	.block_h = { 1, 0, 0 },
+	.hsub = 1,
+	.vsub = 1,
+};
+
+static int gud_get_vendor_descriptor(struct usb_interface *interface,
+				     struct gud_drm_display_descriptor *desc)
+{
+	u8 ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
+	struct usb_device *usb = interface_to_usbdev(interface);
+	void *buf;
+	int ret;
+
+	buf = kmalloc(sizeof(*desc), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(usb, usb_rcvctrlpipe(usb, 0),
+			      USB_REQ_GET_DESCRIPTOR,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			      GUD_DRM_USB_DT_DISPLAY << 8, ifnum, buf, sizeof(*desc),
+			      USB_CTRL_GET_TIMEOUT);
+
+	memcpy(desc, buf, sizeof(*desc));
+	kfree(buf);
+
+	if (ret < 0)
+		return ret;
+
+	if (ret != sizeof(*desc) || desc->bLength != sizeof(*desc) ||
+	    desc->bDescriptorType != GUD_DRM_USB_DT_DISPLAY)
+		return -ENODATA;
+
+	DRM_DEV_DEBUG_DRIVER(&interface->dev,
+			     "Version=%u Compression=0x%x NumFormats=%u NumConnectors=%u MaxBufferSizeOrder=%u\n",
+			     desc->bVersion, desc->bCompression, desc->bNumFormats,
+			     desc->bNumConnectors, desc->bMaxBufferSizeOrder);
+
+	if (desc->bVersion != 1 || !desc->bNumFormats || !desc->bNumConnectors ||
+	    !desc->bMaxBufferSizeOrder || !desc->dwMaxWidth || !desc->dwMaxHeight ||
+	    le32_to_cpu(desc->dwMinWidth) > le32_to_cpu(desc->dwMaxWidth) ||
+	    le32_to_cpu(desc->dwMinHeight) > le32_to_cpu(desc->dwMaxHeight))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int gud_usb_control_msg(struct gud_drm_device *gdrm, bool in,
+			       u8 request, u16 value, void *buf, size_t len)
+{
+	u8 requesttype = USB_TYPE_VENDOR | USB_RECIP_INTERFACE;
+	unsigned int pipe;
+	int idx, ret;
+
+	if (!drm_dev_enter(&gdrm->drm, &idx))
+		return -ENODEV;
+
+	if (in) {
+		pipe = usb_rcvctrlpipe(gdrm->usb, 0);
+		requesttype |= USB_DIR_IN;
+	} else {
+		pipe = usb_sndctrlpipe(gdrm->usb, 0);
+		requesttype |= USB_DIR_OUT;
+	}
+
+	ret = usb_control_msg(gdrm->usb, pipe, request, requesttype, value,
+			      gdrm->ifnum, buf, len, USB_CTRL_GET_TIMEOUT);
+	if (ret >= 0) {
+		if (ret != len)
+			ret = -EIO;
+		else
+			ret = 0;
+	}
+
+	drm_dev_exit(idx);
+
+	return ret;
+}
+
+static int gud_usb_get_status(struct gud_drm_device *gdrm)
+{
+	struct gud_drm_req_get_status *status = gdrm->status_buf;
+	int ret, status_retries = 2000 / 5; /* maximum wait ~2 seconds */
+	unsigned long delay = 500;
+
+	/*
+	 * Poll due to lack of data/status stage control on the gadget side.
+	 *
+	 * If we did not use polling and gave up here after waiting 2 seconds,
+	 * the worker in the gadget would finally get to queuing up the status
+	 * respons, but by that time the host has moved on. The gadget side
+	 * (at least dwc2) would now be left in a non-recoverable state.
+	 *
+	 * Worst case commit timeout in DRM can be tens of seconds (wait for
+	 * various _done completions).
+	 */
+	while (status_retries--) {
+		ret = gud_usb_control_msg(gdrm, true, USB_REQ_GET_STATUS, 0,
+					  status, sizeof(*status));
+		if (ret)
+			return ret;
+
+		if (!(status->flags & GUD_DRM_STATUS_PENDING))
+			return -status->errno;
+
+		usleep_range(delay, delay + 1000);
+
+		if (delay < 4500)
+			delay += 1000;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int gud_usb_transfer(struct gud_drm_device *gdrm, bool in, u8 request,
+			    u16 index, void *buf, size_t len)
+{
+	int ret;
+
+	drm_dbg(&gdrm->drm, "%s: request=0x%x index=%u len=%zu\n",
+		in ? "get" : "set", request, index, len);
+
+	if (len > GUD_DRM_MAX_TRANSFER_SIZE)
+		return -ENOMEM;
+
+	mutex_lock(&gdrm->ctrl_lock);
+
+	if (!in && buf)
+		memcpy(gdrm->ctrl_msg_buf, buf, len);
+
+	ret = gud_usb_control_msg(gdrm, in, request, index, gdrm->ctrl_msg_buf, len);
+
+	/*
+	 * OUT transfers are processed in a worker on the gadget side after
+	 * reception so we always need to check status. IN transfers are
+	 * processed in the interrupt handler and will halt on error letting us
+	 * know something went wrong.
+	 */
+	if (ret || !in) {
+		ret = gud_usb_get_status(gdrm);
+		if (ret)
+			goto unlock;
+	}
+
+	if (in && buf)
+		memcpy(buf, gdrm->ctrl_msg_buf, len);
+unlock:
+	mutex_unlock(&gdrm->ctrl_lock);
+
+	if (ret) {
+		drm_dbg(&gdrm->drm, "ret=%d\n", ret);
+		gdrm->stats_num_errors++;
+	}
+
+	return ret;
+}
+
+int gud_drm_usb_get(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len)
+{
+	return gud_usb_transfer(gdrm, true, request, index, buf, len);
+}
+
+int gud_drm_usb_set(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len)
+{
+	return gud_usb_transfer(gdrm, false, request, index, buf, len);
+}
+
+int gud_drm_usb_write8(struct gud_drm_device *gdrm, u8 request, u8 val)
+{
+	return gud_drm_usb_set(gdrm, request, 0, &val, sizeof(val));
+}
+
+static int gud_drm_usb_read32(struct gud_drm_device *gdrm, u8 request,
+			      u32 *vals, unsigned int num_vals)
+{
+	unsigned int i;
+	int ret;
+
+	ret = gud_drm_usb_get(gdrm, request, 0, vals, num_vals * sizeof(*vals));
+	if (ret)
+		return ret;
+
+	for (i = 0; i < num_vals; i++)
+		vals[i] = le32_to_cpu((__le32)vals[i]);
+
+	return 0;
+}
+
+static int gud_drm_get_properties(struct gud_drm_device *gdrm, unsigned int num_properties)
+{
+	struct gud_drm_property *properties;
+	unsigned int i;
+	int ret;
+
+	if (!num_properties)
+		return 0;
+
+	gdrm->properties = kcalloc(num_properties, sizeof(*gdrm->properties), GFP_KERNEL);
+	if (!gdrm->properties)
+		return -ENOMEM;
+
+	properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL);
+	if (!properties)
+		return -ENOMEM;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_PROPERTIES, 0,
+			      properties, num_properties * sizeof(*properties));
+	if (ret)
+		goto out;
+
+	for (i = 0; i < num_properties; i++) {
+		u16 prop = le16_to_cpu(properties[i].prop);
+		u64 val = le64_to_cpu(properties[i].val);
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_ROTATION:
+			ret = drm_plane_create_rotation_property(&gdrm->pipe.plane,
+								 DRM_MODE_ROTATE_0, val);
+			break;
+		default:
+			/* New ones might show up in future devices, skip those we don't know. */
+			drm_dbg(&gdrm->drm, "Unknown property: %u\n", prop);
+			continue;
+		}
+
+		if (ret)
+			goto out;
+
+		gdrm->properties[gdrm->num_properties++] = prop;
+	}
+out:
+	kfree(properties);
+
+	return ret;
+}
+
+static void gud_drm_driver_release(struct drm_device *drm)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(drm);
+
+	drm_dbg(&gdrm->drm, "%s:\n", __func__);
+
+	drm_mode_config_cleanup(drm);
+	drm_dev_fini(drm);
+
+	kfree(gdrm->properties);
+	vfree(gdrm->compress_buf);
+	kfree(gdrm->bulk_buf);
+	kfree(gdrm->ctrl_msg_buf);
+
+	mutex_destroy(&gdrm->ctrl_lock);
+	mutex_destroy(&gdrm->damage_lock);
+
+	kfree(gdrm);
+}
+
+static struct drm_gem_object *
+gud_drm_driver_gem_create_object(struct drm_device *dev, size_t size)
+{
+	struct drm_gem_shmem_object *shmem;
+
+	shmem = kzalloc(sizeof(*shmem), GFP_KERNEL);
+	if (!shmem)
+		return NULL;
+
+	/*
+	 * This doesn't make a difference on x86, but on ARM (pi4) it was
+	 * necessary to avoid black lines all over and it made it possible to
+	 * compress directly from the framebuffer without performance drop.
+	 */
+	shmem->map_cached = true;
+
+	return &shmem->base;
+}
+
+static int gud_drm_stats_debugfs(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct gud_drm_device *gdrm = to_gud_drm_device(node->minor->dev);
+	char buf[10];
+
+	string_get_size(gdrm->bulk_len, 1, STRING_UNITS_2, buf, sizeof(buf));
+	seq_printf(m, "Max buffer size: %s\n", buf);
+	seq_printf(m, "Number of errors:  %u\n", gdrm->stats_num_errors);
+
+	seq_puts(m, "Compression:      ");
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4)
+		seq_puts(m, " lz4");
+	seq_puts(m, "\n");
+
+	if (gdrm->compression) {
+		u64 remainder;
+		u64 ratio = div64_u64_rem(gdrm->stats_length, gdrm->stats_actual_length,
+					  &remainder);
+		u64 ratio_frac = div64_u64(remainder * 10, gdrm->stats_actual_length);
+
+		seq_printf(m, "Compression ratio: %llu.%llu\n", ratio, ratio_frac);
+	}
+
+	return 0;
+}
+
+static const struct drm_info_list gud_drm_debugfs_list[] = {
+	{ "stats", gud_drm_stats_debugfs, 0, NULL },
+};
+
+static int gud_drm_driver_debugfs_init(struct drm_minor *minor)
+{
+	return drm_debugfs_create_files(gud_drm_debugfs_list,
+					ARRAY_SIZE(gud_drm_debugfs_list),
+					minor->debugfs_root, minor);
+}
+
+static const struct drm_simple_display_pipe_funcs gud_drm_pipe_funcs = {
+	.check      = gud_drm_pipe_check,
+	.update	    = gud_drm_pipe_update,
+	.prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
+};
+
+static const struct drm_mode_config_funcs gud_drm_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create_with_dirty,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static const uint64_t gud_drm_pipe_modifiers[] = {
+	DRM_FORMAT_MOD_LINEAR,
+	DRM_FORMAT_MOD_INVALID
+};
+
+DEFINE_DRM_GEM_FOPS(gud_drm_fops);
+
+static struct drm_driver gud_drm_driver = {
+	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	.fops			= &gud_drm_fops,
+	.release		= gud_drm_driver_release,
+	.gem_create_object	= gud_drm_driver_gem_create_object,
+	DRM_GEM_SHMEM_DRIVER_OPS,
+	.debugfs_init		= gud_drm_driver_debugfs_init,
+
+	.name			= "gud_drm",
+	.desc			= "Generic USB Display",
+	.date			= "20200422",
+	.major			= 1,
+	.minor			= 0,
+};
+
+static int gud_drm_probe(struct usb_interface *interface,
+			 const struct usb_device_id *id)
+{
+	const struct drm_format_info *xrgb8888_emulation_format = NULL;
+	u32 *formats, *formats_dev, num_connectors, num_formats = 0;
+	bool rgb565_supported = false, rgb8888_supported = false;
+	struct usb_device *usb = interface_to_usbdev(interface);
+	struct usb_endpoint_descriptor *bulk_out;
+	struct gud_drm_display_descriptor desc;
+	struct device *dev = &interface->dev;
+	struct gud_drm_device *gdrm;
+	struct drm_device *drm;
+	size_t max_buffer_size;
+	int ret, i;
+
+	ret = usb_find_bulk_out_endpoint(interface->cur_altsetting, &bulk_out);
+	if (ret)
+		return ret;
+
+	ret = gud_get_vendor_descriptor(interface, &desc);
+	if (ret) {
+		DRM_DEV_DEBUG_DRIVER(dev, "Not a display interface: ret=%d\n", ret);
+		return -ENODEV;
+	}
+
+	num_connectors = desc.bNumConnectors;
+	max_buffer_size = 1 << desc.bMaxBufferSizeOrder;
+
+	gdrm = kzalloc(sizeof(*gdrm), GFP_KERNEL);
+	if (!gdrm)
+		return -ENOMEM;
+
+	gdrm->usb = usb;
+	gdrm->ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
+	gdrm->compression = desc.bCompression & GUD_DRM_COMPRESSION_LZ4;
+
+	drm = &gdrm->drm;
+	ret = devm_drm_dev_init(dev, drm, &gud_drm_driver);
+	if (ret) {
+		kfree(gdrm);
+		return ret;
+	}
+
+	drm_mode_config_init(drm);
+	drm->mode_config.funcs = &gud_drm_mode_config_funcs;
+
+	mutex_init(&gdrm->ctrl_lock);
+	mutex_init(&gdrm->damage_lock);
+	INIT_WORK(&gdrm->work, gud_drm_work);
+	gud_drm_clear_damage(gdrm);
+
+	/*
+	 * devm_kmalloc() places struct devres at the beginning of the buffer it
+	 * allocates. This can waste a lot of memory when allocating
+	 * power-of-two sized buffers. Asking for 4k would actually allocate 8k.
+	 */
+
+	gdrm->ctrl_msg_buf = kmalloc(GUD_DRM_MAX_TRANSFER_SIZE, GFP_KERNEL);
+	gdrm->status_buf = devm_kzalloc(dev, sizeof(struct gud_drm_req_get_status), GFP_KERNEL);
+	if (!gdrm->ctrl_msg_buf || !gdrm->status_buf)
+		return -ENOMEM;
+retry:
+	gdrm->bulk_buf = kmalloc(max_buffer_size, GFP_KERNEL);
+	if (!gdrm->bulk_buf) {
+		max_buffer_size /= 2;
+		if (max_buffer_size < SZ_2M) { /* Give up if we can't do 1024x768 RGB565 */
+			return -ENOMEM;
+		}
+		goto retry;
+	}
+
+	gdrm->bulk_pipe = usb_sndbulkpipe(gdrm->usb, usb_endpoint_num(bulk_out));
+	gdrm->bulk_len = max_buffer_size;
+
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4) {
+		gdrm->lz4_comp_mem = devm_kmalloc(dev, LZ4_MEM_COMPRESS, GFP_KERNEL);
+		if (!gdrm->lz4_comp_mem)
+			return -ENOMEM;
+
+		gdrm->compress_buf = vmalloc(gdrm->bulk_len);
+		if (!gdrm->compress_buf)
+			return -ENOMEM;
+	}
+
+	drm->mode_config.min_width = le32_to_cpu(desc.dwMinWidth);
+	drm->mode_config.max_width = le32_to_cpu(desc.dwMaxWidth);
+	drm->mode_config.min_height = le32_to_cpu(desc.dwMinHeight);
+	drm->mode_config.max_height = le32_to_cpu(desc.dwMaxHeight);
+
+	formats_dev = devm_kmalloc_array(dev, desc.bNumFormats, sizeof(u32), GFP_KERNEL);
+	/* Add room for emulated XRGB8888 */
+	formats = devm_kmalloc_array(dev, desc.bNumFormats + 1, sizeof(u32), GFP_KERNEL);
+	if (!formats_dev || !formats)
+		return -ENOMEM;
+
+	ret = gud_drm_usb_read32(gdrm, GUD_DRM_USB_REQ_GET_FORMATS, formats_dev, desc.bNumFormats);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < desc.bNumFormats; i++) {
+		const struct drm_format_info *fmt_info;
+		u32 format = formats_dev[i];
+
+		if (format == GUD_DRM_FORMAT_R1) {
+			fmt_info = &gud_drm_format_r1;
+		} else {
+			/* This will trigger a WARN for unknown formats... */
+			fmt_info = drm_format_info(format);
+			if (!fmt_info) {
+				drm_dbg(drm, "Unknown format: 0x%x\n", format);
+				continue;
+			}
+		}
+
+		switch (format) {
+		case DRM_FORMAT_XRGB8888:
+			fallthrough;
+		case DRM_FORMAT_ARGB8888:
+			rgb8888_supported = true;
+			break;
+		case DRM_FORMAT_RGB888:
+			fallthrough;
+		case DRM_FORMAT_BGR888:
+			drm_dbg(drm, "24-bit formats are not supported.\n");
+			continue;
+		case DRM_FORMAT_RGB565:
+			rgb565_supported = true;
+			if (!xrgb8888_emulation_format)
+				xrgb8888_emulation_format = fmt_info;
+			break;
+		case GUD_DRM_FORMAT_R1:
+			if (!xrgb8888_emulation_format)
+				xrgb8888_emulation_format = fmt_info;
+			/* Internal, not for userspace */
+			continue;
+		}
+
+		formats[num_formats++] = format;
+	}
+
+	if (!num_formats && !xrgb8888_emulation_format) {
+		dev_err(dev, "No supported formats found\n");
+		return -ENOENT;
+	}
+
+	/* Prefer speed over color depth */
+	if (rgb565_supported)
+		drm->mode_config.preferred_depth = 16;
+
+	if (!rgb8888_supported && xrgb8888_emulation_format) {
+		gdrm->xrgb8888_emulation_format = xrgb8888_emulation_format;
+		formats[num_formats++] = DRM_FORMAT_XRGB8888;
+	}
+
+	ret = drm_simple_display_pipe_init(drm, &gdrm->pipe, &gud_drm_pipe_funcs,
+					   formats, num_formats,
+					   gud_drm_pipe_modifiers, NULL);
+	if (ret)
+		return ret;
+
+	devm_kfree(dev, formats);
+	devm_kfree(dev, formats_dev);
+
+	ret = gud_drm_get_properties(gdrm, desc.bNumProperties);
+	if (ret)
+		return ret;
+
+	drm_plane_enable_fb_damage_clips(&gdrm->pipe.plane);
+
+	for (i = 0; i < num_connectors; i++) {
+		ret = gud_drm_connector_create(gdrm, i);
+		if (ret)
+			return ret;
+	}
+
+	drm_mode_config_reset(drm);
+
+	usb_set_intfdata(interface, gdrm);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		return ret;
+
+	drm_kms_helper_poll_init(drm);
+
+	drm_fbdev_generic_setup(drm, 16);
+
+	return 0;
+}
+
+static void gud_drm_disconnect(struct usb_interface *interface)
+{
+	struct gud_drm_device *gdrm = usb_get_intfdata(interface);
+	struct drm_device *drm = &gdrm->drm;
+
+	drm_dbg(drm, "%s:\n", __func__);
+
+	drm_kms_helper_poll_fini(drm);
+	drm_dev_unplug(drm);
+	drm_atomic_helper_shutdown(drm);
+}
+
+static int gud_drm_suspend(struct usb_interface *interface, pm_message_t message)
+{
+	struct gud_drm_device *gdrm = usb_get_intfdata(interface);
+
+	return drm_mode_config_helper_suspend(&gdrm->drm);
+}
+
+static int gud_drm_resume(struct usb_interface *interface)
+{
+	struct gud_drm_device *gdrm = usb_get_intfdata(interface);
+
+	drm_mode_config_helper_resume(&gdrm->drm);
+
+	return 0;
+}
+
+static const struct usb_device_id gud_drm_table[] = {
+	/*
+	 * FIXME:
+	 * Apply for a proper pid: https://github.com/openmoko/openmoko-usb-oui
+	 */
+	{ USB_DEVICE_INTERFACE_CLASS(0x1d50, 0x6150, USB_CLASS_VENDOR_SPEC) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, gud_drm_table);
+
+static struct usb_driver gud_drm_usb_driver = {
+	.name		= "gud_drm",
+	.probe		= gud_drm_probe,
+	.disconnect	= gud_drm_disconnect,
+	.id_table	= gud_drm_table,
+	.suspend	= gud_drm_suspend,
+	.resume		= gud_drm_resume,
+	.reset_resume	= gud_drm_resume,
+};
+
+module_usb_driver(gud_drm_usb_driver);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/gud/gud_drm_internal.h b/drivers/gpu/drm/gud/gud_drm_internal.h
new file mode 100644
index 000000000000..30d86bb0389d
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_internal.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef __LINUX_GUD_DRM_INTERNAL_H
+#define __LINUX_GUD_DRM_INTERNAL_H
+
+#include <linux/workqueue.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+
+#include <drm/drm_simple_kms_helper.h>
+
+struct gud_drm_device {
+	struct drm_device drm;
+	struct drm_simple_display_pipe pipe;
+	struct work_struct work;
+	struct usb_device *usb;
+	u8 ifnum;
+	const struct drm_format_info *xrgb8888_emulation_format;
+
+	u16 *properties;
+	unsigned int num_properties;
+
+	unsigned int bulk_pipe;
+	void *bulk_buf;
+	size_t bulk_len;
+
+	u8 compression;
+	void *lz4_comp_mem;
+	void *compress_buf;
+
+	u64 stats_length;
+	u64 stats_actual_length;
+	unsigned int stats_num_errors;
+
+	struct mutex ctrl_lock; /* Serialize req and status transfers */
+	void *ctrl_msg_buf;
+	void *status_buf;
+
+	struct mutex damage_lock; /* Protects the following members: */
+	struct drm_framebuffer *fb;
+	struct drm_rect damage;
+};
+
+static inline struct gud_drm_device *to_gud_drm_device(struct drm_device *drm)
+{
+	return container_of(drm, struct gud_drm_device, drm);
+}
+
+int gud_drm_usb_get(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len);
+int gud_drm_usb_set(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len);
+int gud_drm_usb_write8(struct gud_drm_device *gdrm, u8 request, u8 val);
+
+void gud_drm_clear_damage(struct gud_drm_device *gdrm);
+void gud_drm_work(struct work_struct *work);
+int gud_drm_pipe_check(struct drm_simple_display_pipe *pipe,
+		       struct drm_plane_state *new_plane_state,
+		       struct drm_crtc_state *new_crtc_state);
+void gud_drm_pipe_update(struct drm_simple_display_pipe *pipe,
+			 struct drm_plane_state *old_state);
+
+int gud_drm_connector_fill_properties(struct drm_connector *connector,
+				      struct drm_connector_state *connector_state,
+				      struct gud_drm_property *properties);
+int gud_drm_connector_create(struct gud_drm_device *gdrm, unsigned int index);
+
+#endif
diff --git a/drivers/gpu/drm/gud/gud_drm_pipe.c b/drivers/gpu/drm/gud/gud_drm_pipe.c
new file mode 100644
index 000000000000..51cf9b55598f
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_pipe.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/lz4.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_format_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_rect.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+static bool gud_drm_is_big_endian(void)
+{
+#if defined(__BIG_ENDIAN)
+	return true;
+#else
+	return false;
+#endif
+}
+
+static size_t gud_drm_xrgb8888_to_r124(u8 *dst, const struct drm_format_info *format,
+				       void *src, struct drm_framebuffer *fb,
+				       struct drm_rect *rect)
+{
+	unsigned int block_width = drm_format_info_block_width(format, 0);
+	unsigned int bits_per_pixel = 8 / block_width;
+	unsigned int x, y, width, height;
+	u8 *p, *block = dst; /* Assign to silence compiler warning */
+	size_t len;
+	void *buf;
+
+	WARN_ON_ONCE(format->char_per_block[0] != 1);
+
+	/* Start on a byte boundary */
+	rect->x1 = ALIGN_DOWN(rect->x1, block_width);
+	width = drm_rect_width(rect);
+	height = drm_rect_height(rect);
+	len = drm_format_info_min_pitch(format, 0, width) * height;
+
+	buf = kmalloc(width * height, GFP_KERNEL);
+	if (!buf)
+		return len; /* To keep logic simple, just transmit garbage */
+
+	drm_fb_xrgb8888_to_gray8(buf, src, fb, rect);
+
+	p = buf;
+	for (y = 0; y < drm_rect_height(rect); y++) {
+		for (x = 0; x < drm_rect_width(rect); x++) {
+			if (!(x % block_width)) {
+				block = dst++;
+				*block = 0;
+			}
+
+			*block <<= bits_per_pixel;
+			*block |= (*p++) >> (8 - bits_per_pixel);
+		}
+	}
+
+	kfree(buf);
+
+	return len;
+}
+
+static int gud_drm_fb_flush(struct gud_drm_device *gdrm, struct drm_framebuffer *fb,
+			    const struct drm_format_info *format, struct drm_rect *rect)
+{
+	struct dma_buf_attachment *import_attach = fb->obj[0]->import_attach;
+	struct gud_drm_req_set_buffer req;
+	size_t trlen, len;
+	int actual_length;
+	void *vaddr, *buf;
+	int ret = 0;
+
+	drm_dbg(&gdrm->drm, "Flushing [FB:%d] " DRM_RECT_FMT " imported=%s\n",
+		fb->base.id, DRM_RECT_ARG(rect), import_attach ? "yes" : "no");
+
+	len = drm_format_info_min_pitch(format, 0, drm_rect_width(rect)) * drm_rect_height(rect);
+	if (len > gdrm->bulk_len)
+		return -E2BIG;
+
+	vaddr = drm_gem_shmem_vmap(fb->obj[0]);
+	if (!vaddr)
+		return -ENOMEM;
+
+	if (import_attach) {
+		ret = dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE);
+		if (ret)
+			goto vunmap;
+	}
+
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4)
+		buf = gdrm->compress_buf;
+	else
+		buf = gdrm->bulk_buf;
+
+	/*
+	 * Imported buffers are assumed to be write-combined and thus uncached
+	 * with slow reads (at least on ARM).
+	 */
+	if (format != fb->format) {
+		if (format->format == GUD_DRM_FORMAT_R1)
+			len = gud_drm_xrgb8888_to_r124(buf, format, vaddr, fb, rect);
+		else if (format->format == DRM_FORMAT_RGB565)
+			drm_fb_xrgb8888_to_rgb565(buf, vaddr, fb, rect, gud_drm_is_big_endian());
+	} else if (gud_drm_is_big_endian() && format->cpp[0] > 1) {
+		drm_fb_swab(buf, vaddr, fb, rect, !import_attach);
+	} else if (!import_attach && gdrm->compression && len == fb->height * fb->pitches[0]) {
+		/* can compress directly from the framebuffer */
+		buf = vaddr;
+	} else {
+		drm_fb_memcpy(buf, vaddr, fb, rect);
+	}
+
+	req.x = cpu_to_le32(rect->x1);
+	req.y = cpu_to_le32(rect->y1);
+	req.width = cpu_to_le32(drm_rect_width(rect));
+	req.height = cpu_to_le32(drm_rect_height(rect));
+	req.length = cpu_to_le32(len);
+
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4) {
+		ret = LZ4_compress_default(buf, gdrm->bulk_buf, len, len, gdrm->lz4_comp_mem);
+		if (ret > 0)
+			req.compression = GUD_DRM_COMPRESSION_LZ4;
+	}
+
+	trlen = len;
+
+	if (ret > 0) {
+		req.compressed_length = cpu_to_le32(ret);
+		trlen = ret;
+	} else if (buf == gdrm->compress_buf) {
+		/*
+		 * Compression failed (buffer didn't compress well).
+		 * compress_buf is vmalloc'ed so we need to copy.
+		 */
+		memcpy(gdrm->bulk_buf, gdrm->compress_buf, len);
+	}
+
+	if (import_attach)
+		dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE);
+
+	gdrm->stats_length += len;
+	/* Did it wrap around? */
+	if (gdrm->stats_length <= len && gdrm->stats_actual_length) {
+		gdrm->stats_length = len;
+		gdrm->stats_actual_length = 0;
+	}
+	gdrm->stats_actual_length += trlen;
+
+	/*
+	 * This will wait if decompress/copy from the previous flush is still in
+	 * process on the gadget side.
+	 */
+	ret = gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_BUFFER, 0, &req, sizeof(req));
+	if (ret)
+		goto vunmap;
+
+	ret = usb_bulk_msg(gdrm->usb, gdrm->bulk_pipe, gdrm->bulk_buf, trlen,
+			   &actual_length, msecs_to_jiffies(3000));
+	if (!ret && trlen != actual_length)
+		ret = -EIO;
+	if (ret)
+		gdrm->stats_num_errors++;
+vunmap:
+	drm_gem_shmem_vunmap(fb->obj[0], vaddr);
+
+	return ret;
+}
+
+void gud_drm_clear_damage(struct gud_drm_device *gdrm)
+{
+	gdrm->damage.x1 = INT_MAX;
+	gdrm->damage.y1 = INT_MAX;
+	gdrm->damage.x2 = 0;
+	gdrm->damage.y2 = 0;
+}
+
+void gud_drm_work(struct work_struct *work)
+{
+	struct gud_drm_device *gdrm = container_of(work, struct gud_drm_device, work);
+	const struct drm_format_info *format;
+	struct drm_framebuffer *fb;
+	struct drm_rect damage;
+	unsigned int i, lines;
+	int idx, ret = 0;
+	size_t pitch;
+
+	if (!drm_dev_enter(&gdrm->drm, &idx))
+		return;
+
+	mutex_lock(&gdrm->damage_lock);
+	fb = gdrm->fb;
+	gdrm->fb = NULL;
+	damage = gdrm->damage;
+	gud_drm_clear_damage(gdrm);
+	mutex_unlock(&gdrm->damage_lock);
+
+	if (!fb)
+		goto out;
+
+	format = fb->format;
+	if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format)
+		format = gdrm->xrgb8888_emulation_format;
+
+	/* Split update if it's too big */
+	pitch = drm_format_info_min_pitch(format, 0, drm_rect_width(&damage));
+	lines = drm_rect_height(&damage);
+
+	if (gdrm->bulk_len < lines * pitch)
+		lines = gdrm->bulk_len / pitch;
+
+	for (i = 0; i < DIV_ROUND_UP(drm_rect_height(&damage), lines); i++) {
+		struct drm_rect rect = damage;
+
+		rect.y1 += i * lines;
+		rect.y2 = min_t(u32, rect.y1 + lines, damage.y2);
+
+		ret = gud_drm_fb_flush(gdrm, fb, format, &rect);
+		if (ret &&
+		    (ret != -ENODEV && ret != -ECONNRESET && ret != -ESHUTDOWN && ret != EPROTO))
+			dev_err_once(fb->dev->dev, "Failed to flush framebuffer: error=%d\n", ret);
+	}
+
+	drm_framebuffer_put(fb);
+out:
+	drm_dev_exit(idx);
+}
+
+static void gud_drm_fb_queue_damage(struct gud_drm_device *gdrm,
+				    struct drm_framebuffer *fb,
+				    struct drm_rect *damage)
+{
+	struct drm_framebuffer *old_fb = NULL;
+
+	mutex_lock(&gdrm->damage_lock);
+
+	if (fb != gdrm->fb) {
+		old_fb = gdrm->fb;
+		drm_framebuffer_get(fb);
+		gdrm->fb = fb;
+	}
+
+	gdrm->damage.x1 = min(gdrm->damage.x1, damage->x1);
+	gdrm->damage.y1 = min(gdrm->damage.y1, damage->y1);
+	gdrm->damage.x2 = max(gdrm->damage.x2, damage->x2);
+	gdrm->damage.y2 = max(gdrm->damage.y2, damage->y2);
+
+	mutex_unlock(&gdrm->damage_lock);
+
+	queue_work(system_long_wq, &gdrm->work);
+
+	if (old_fb)
+		drm_framebuffer_put(old_fb);
+}
+
+int gud_drm_pipe_check(struct drm_simple_display_pipe *pipe,
+		       struct drm_plane_state *new_plane_state,
+		       struct drm_crtc_state *new_crtc_state)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(pipe->crtc.dev);
+	struct drm_plane_state *old_plane_state = pipe->plane.state;
+	const struct drm_display_mode *mode = &new_crtc_state->mode;
+	struct drm_atomic_state *state = new_plane_state->state;
+	struct drm_framebuffer *old_fb = old_plane_state->fb;
+	struct drm_connector_state *connector_state = NULL;
+	struct drm_framebuffer *fb = new_plane_state->fb;
+	const struct drm_format_info *format = fb->format;
+	struct gud_drm_req_set_state *req;
+	struct drm_connector *connector;
+	int idx, ret, num_properties;
+	unsigned int i;
+	size_t len;
+
+	if (WARN_ON_ONCE(!fb))
+		return -EINVAL;
+
+	if (old_plane_state->rotation != new_plane_state->rotation)
+		new_crtc_state->mode_changed = true;
+
+	if (old_fb && old_fb->format != format)
+		new_crtc_state->mode_changed = true;
+
+	if (!new_crtc_state->mode_changed && !new_crtc_state->connectors_changed)
+		return 0;
+
+	/* Only one connector is supported */
+	if (hweight32(new_crtc_state->connector_mask) != 1)
+		return -EINVAL;
+
+	if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format)
+		format = gdrm->xrgb8888_emulation_format;
+
+	for_each_new_connector_in_state(state, connector, connector_state, i)
+		break;
+
+	if (!connector_state) {
+		struct drm_connector_list_iter conn_iter;
+
+		/* We always send the full state to the device, so get the connector state */
+
+		drm_connector_list_iter_begin(pipe->crtc.dev, &conn_iter);
+		drm_for_each_connector_iter(connector, &conn_iter) {
+			if (new_crtc_state->connector_mask & drm_connector_mask(connector))
+				break;
+		}
+		drm_connector_list_iter_end(&conn_iter);
+
+		if (WARN_ON_ONCE(!connector))
+			return -ENOENT;
+
+		connector_state = drm_atomic_get_connector_state(state, connector);
+		if (IS_ERR(connector_state))
+			return PTR_ERR(connector_state);
+	}
+
+	num_properties = gud_drm_connector_fill_properties(connector, NULL, NULL);
+	if (num_properties < 0)
+		return num_properties;
+
+	num_properties += gdrm->num_properties;
+
+	len = struct_size(req, properties, num_properties);
+	req = kzalloc(len, GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	gud_drm_from_display_mode(&req->mode, mode);
+
+	req->format = cpu_to_le32(format->format);
+	req->connector = drm_connector_index(connector);
+	req->num_properties = num_properties;
+
+	num_properties = gud_drm_connector_fill_properties(connector, connector_state,
+							   req->properties);
+
+	for (i = 0; i < gdrm->num_properties; i++) {
+		u16 prop = gdrm->properties[i];
+		u64 val;
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_ROTATION:
+			val = new_plane_state->rotation;
+			break;
+		default:
+			WARN_ON_ONCE(1);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		req->properties[num_properties + i].prop = cpu_to_le16(prop);
+		req->properties[num_properties + i].val = cpu_to_le64(val);
+	}
+
+	if (!drm_dev_enter(fb->dev, &idx)) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	ret = gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_STATE_CHECK, 0, req, len);
+
+	drm_dev_exit(idx);
+out:
+	kfree(req);
+
+	return ret;
+}
+
+void gud_drm_pipe_update(struct drm_simple_display_pipe *pipe,
+			 struct drm_plane_state *old_state)
+{
+	struct drm_device *drm = pipe->crtc.dev;
+	struct gud_drm_device *gdrm = to_gud_drm_device(drm);
+	struct drm_plane_state *state = pipe->plane.state;
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_rect damage;
+	int idx;
+
+	if (!drm_dev_enter(drm, &idx))
+		return;
+
+	if (!old_state->fb)
+		gud_drm_usb_write8(gdrm, GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE, 1);
+
+	if (fb && (crtc->state->mode_changed || crtc->state->connectors_changed))
+		gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_STATE_COMMIT, 0, NULL, 0);
+
+	if (crtc->state->active_changed)
+		gud_drm_usb_write8(gdrm, GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE, crtc->state->active);
+
+	if (drm_atomic_helper_damage_merged(old_state, state, &damage))
+		gud_drm_fb_queue_damage(gdrm, fb, &damage);
+
+	if (!fb) {
+		cancel_work_sync(&gdrm->work);
+
+		mutex_lock(&gdrm->damage_lock);
+		if (gdrm->fb) {
+			drm_framebuffer_put(gdrm->fb);
+			gdrm->fb = NULL;
+		}
+		gud_drm_clear_damage(gdrm);
+		mutex_unlock(&gdrm->damage_lock);
+
+		gud_drm_usb_write8(gdrm, GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE, 0);
+	}
+
+	drm_dev_exit(idx);
+}
diff --git a/include/drm/gud_drm.h b/include/drm/gud_drm.h
new file mode 100644
index 000000000000..15bb30577b57
--- /dev/null
+++ b/include/drm/gud_drm.h
@@ -0,0 +1,356 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#ifndef __LINUX_GUD_DRM_H
+#define __LINUX_GUD_DRM_H
+
+#include <drm/drm_modes.h>
+#include <linux/types.h>
+#include <uapi/drm/drm_fourcc.h>
+#include <uapi/linux/usb/ch9.h>
+
+/*
+ * Maximum size of a control message, fits 120 display modes.
+ * If this needs to increase, the IN side in f_gud_drm_setup()
+ * needs fixing.
+ */
+#define GUD_DRM_MAX_TRANSFER_SIZE	SZ_4K
+
+#define GUD_DRM_USB_DT_DISPLAY		(USB_TYPE_VENDOR | 0x4)
+
+/*
+ * struct gud_drm_display_descriptor - Display descriptor
+ * @bLength: Size of descriptor in bytes
+ * @bDescriptorType: DescriptorType (GUD_DRM_USB_DT_DISPLAY)
+ * @bVersion: Protocol version
+ * @bMaxBufferSizeOrder: Maximum buffer size the device can handle as log2
+ * @bmFlags: Currently unused, should be set to zero
+ * @bCompression: Supported compression types
+ * @dwMinWidth: Minimum pixel width the controller can handle
+ * @dwMaxWidth: Maximum width
+ * @dwMinHeight: Minimum height
+ * @dwMaxHeight: Maximum height
+ * @bNumFormats: Number of supported pixel formats
+ * @bNumProperties: Number of properties that are not connector porperties
+ * @bNumConnectors: Number of connectors
+ *
+ * Devices that have only one display mode will have dwMinWidth == dwMaxWidth
+ * and dwMinHeight == dwMaxHeight.
+ *
+ */
+struct gud_drm_display_descriptor {
+	__u8 bLength;
+	__u8 bDescriptorType;
+
+	__u8 bVersion;
+	__u8 bMaxBufferSizeOrder;
+	__le32 bmFlags;
+
+	__u8 bCompression;
+#define GUD_DRM_COMPRESSION_LZ4		BIT(0)
+
+	__le32 dwMinWidth;
+	__le32 dwMaxWidth;
+	__le32 dwMinHeight;
+	__le32 dwMaxHeight;
+
+	__u8 bNumFormats;
+	__u8 bNumProperties;
+	__u8 bNumConnectors;
+} __packed;
+
+/*
+ * struct gud_drm_req_get_status - Status request
+ * @flags: Flags
+ * @errno: Linux errno value
+ *
+ * The host keeps polling for status as long as the GUD_DRM_STATUS_PENDING flag
+ * is set (or until timeout). Requested using: USB_REQ_GET_STATUS.
+ */
+struct gud_drm_req_get_status {
+	__u8 flags;
+#define GUD_DRM_STATUS_PENDING	BIT(0)
+	__u8 errno;
+} __packed;
+
+/*
+ * struct gud_drm_property - Property
+ * @prop: Property
+ * @val: Value
+ */
+struct gud_drm_property {
+	__le16 prop;
+	__le64 val;
+} __packed;
+
+/* See &drm_display_mode for the meaning of these fields */
+struct gud_drm_display_mode {
+	__le32 clock;
+	__le16 hdisplay;
+	__le16 hsync_start;
+	__le16 hsync_end;
+	__le16 htotal;
+	__le16 hskew;
+	__le16 vdisplay;
+	__le16 vsync_start;
+	__le16 vsync_end;
+	__le16 vtotal;
+	__le16 vscan;
+	__le32 vrefresh;
+	__le32 flags;
+	__u8 type;
+} __packed;
+
+/*
+ * struct gud_drm_req_get_connector - Connector descriptor
+ * @connector_type: Connector type (DRM_MODE_CONNECTOR_*)
+ * @flags: Flags
+ * @num_properties: Number of supported properties
+ */
+struct gud_drm_req_get_connector {
+	__u8 connector_type;
+
+	__le32 flags;
+#define GUD_DRM_CONNECTOR_FLAGS_POLL	BIT(0)
+
+	__u8 num_properties;
+} __packed;
+
+/*
+ * struct gud_drm_req_get_connector_status - Connector status
+ * @status: Status, see &drm_connector_status
+ * @num_modes: Number of available display modes
+ * @modes_array_checksum: CRC-CCITT checksum of the display mode array in little endian format
+ * @edid_len: Length of EDID data
+ * @edid_checksum: CRC-CCITT checksum of EDID data
+ *
+ * If both @num_modes and @edid_len are zero, connector status is set to
+ * disconnected. If @num_modes is zero, edid is used to create display modes.
+ * If both are set, edid is just passed on to userspace in the EDID connector
+ * property.
+ *
+ * Display modes and EDID are only requested if number/length or crc differs.
+ */
+struct gud_drm_req_get_connector_status {
+	__u8 status;
+#define GUD_DRM_CONNECTOR_STATUS_MASK		0xf /* Only 2 bits are currently used for status */
+#define GUD_DRM_CONNECTOR_STATUS_CHANGED	BIT(7)
+	__u16 num_modes;
+	__u16 edid_len;
+} __packed;
+
+/*
+ * struct gud_drm_req_set_buffer - Set buffer transfer info
+ * @x: X position of rectangle
+ * @y: Y position
+ * @width: Pixel width of rectangle
+ * @height: Pixel height
+ * @length: Buffer length in bytes
+ * @compression: Transfer compression
+ * @compressed_length: Compressed buffer length
+ *
+ * @x, @y, @width and @height specifies the rectangle where the buffer should be
+ * placed inside the framebuffer.
+ */
+struct gud_drm_req_set_buffer {
+	__le32 x;
+	__le32 y;
+	__le32 width;
+	__le32 height;
+
+	__le32 length;
+	__u8 compression;
+	__le32 compressed_length;
+} __packed;
+
+/*
+ * struct gud_drm_req_set_state - Set display state
+ * @mode: Display mode
+ * @format: Pixel format
+ * @connector: Connector index
+ * @num_properties: Number of properties in the state
+ * @properties: Array of properties
+ *
+ * The entire state is transferred each time there's a change.
+ */
+struct gud_drm_req_set_state {
+	struct gud_drm_display_mode mode;
+	__le32 format;
+	__u8 connector;
+	__u8 num_properties;
+	struct gud_drm_property properties[];
+} __packed;
+
+/*
+ * Internal monochrome transfer format presented to userspace as XRGB8888.
+ * Pixel lines are byte aligned.
+ */
+#define GUD_DRM_FORMAT_R1	fourcc_code('R', '1', ' ', ' ')
+
+/* List of supported connector properties: */
+
+/* TV related properties, see &drm_connector and &drm_tv_connector_state */
+#define GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR		1
+#define GUD_DRM_PROPERTY_TV_LEFT_MARGIN			2
+#define GUD_DRM_PROPERTY_TV_RIGHT_MARGIN		3
+#define GUD_DRM_PROPERTY_TV_TOP_MARGIN			4
+#define GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN		5
+/* Number of modes are placed at _SHIFT in val on retrieval */
+#define GUD_DRM_PROPERTY_TV_MODE			6
+  #define GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT   16
+#define GUD_DRM_PROPERTY_TV_BRIGHTNESS			7
+#define GUD_DRM_PROPERTY_TV_CONTRAST			8
+#define GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION		9
+#define GUD_DRM_PROPERTY_TV_OVERSCAN			10
+#define GUD_DRM_PROPERTY_TV_SATURATION			11
+#define GUD_DRM_PROPERTY_TV_HUE				12
+
+/*
+ * Backlight brightness is in the range 0-100 inclusive. The value represents
+ * the human perceptual brightness and not a linear PWM value. 0 is minimum
+ * brightness which should not turn the backlight completely off. The DPMS
+ * connector property should be used to control power which will trigger a
+ * GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE request.
+ *
+ * This is not a real DRM property, but rather a fake one used for the backlight
+ * device. See drm_backlight_register() for more details.
+ */
+#define GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS		13
+
+/* List of supported properties that are not connector propeties: */
+
+/*
+ * Plane rotation. Should return the supported bitmask on
+ * GUD_DRM_USB_REQ_GET_PROPERTIES, see drm_plane_create_rotation_property().
+ */
+#define GUD_DRM_PROPERTY_ROTATION			50
+
+/* USB Control requests: */
+
+/* Get supported pixel formats as an array of fourcc codes. See include/uapi/drm/drm_fourcc.h */
+#define GUD_DRM_USB_REQ_GET_FORMATS			0x40
+
+/* Get supported properties that are not connector propeties as a &gud_drm_property array */
+#define GUD_DRM_USB_REQ_GET_PROPERTIES			0x41
+
+/* Get connector descriptor */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR			0x50
+
+/* Get properties supported by the connector as a &gud_drm_property array */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_PROPERTIES	0x51
+
+/*
+ * Issued when there's a tv.mode property present.
+ * Gets an array of tv.mode enum names each entry of length DRM_PROP_NAME_LEN.
+ */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_TV_MODE_VALUES	0x52
+
+/* When userspace checks status, this is issued first, not used for poll requests. */
+#define GUD_DRM_USB_REQ_SET_CONNECTOR_FORCE_DETECT	0x53
+
+/* Get connector status as &gud_drm_req_get_connector_status. */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_STATUS		0x54
+
+/* Get &gud_drm_display_mode array of supported display modes */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_MODES		0x55
+
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_EDID		0x56
+
+/* Set buffer properties before bulk transfer as &gud_drm_req_set_buffer */
+#define GUD_DRM_USB_REQ_SET_BUFFER			0x60
+
+/* Check display configuration as &gud_drm_req_set_state */
+#define GUD_DRM_USB_REQ_SET_STATE_CHECK			0x61
+
+/* Apply the prevoius _STATE_CHECK configuration */
+#define GUD_DRM_USB_REQ_SET_STATE_COMMIT		0x62
+
+ /* Enable/disable the display controller, value is u8 0/1 */
+#define GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE		0x63
+
+/* Enable/disable display/output (DPMS), value is u8 0/1 */
+#define GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE		0x64
+
+static inline void gud_drm_from_display_mode(struct gud_drm_display_mode *dst,
+					     const struct drm_display_mode *src)
+{
+	u32 flags = src->flags;
+
+	switch (src->picture_aspect_ratio) {
+	case HDMI_PICTURE_ASPECT_4_3:
+		flags |= DRM_MODE_FLAG_PIC_AR_4_3;
+		break;
+	case HDMI_PICTURE_ASPECT_16_9:
+		flags |= DRM_MODE_FLAG_PIC_AR_16_9;
+		break;
+	case HDMI_PICTURE_ASPECT_64_27:
+		flags |= DRM_MODE_FLAG_PIC_AR_64_27;
+		break;
+	case HDMI_PICTURE_ASPECT_256_135:
+		flags |= DRM_MODE_FLAG_PIC_AR_256_135;
+		break;
+	default:
+		flags |= DRM_MODE_FLAG_PIC_AR_NONE;
+		break;
+	}
+
+	dst->clock = cpu_to_le32(src->clock);
+	dst->hdisplay = cpu_to_le16(src->hdisplay);
+	dst->hsync_start = cpu_to_le16(src->hsync_start);
+	dst->hsync_end = cpu_to_le16(src->hsync_end);
+	dst->htotal = cpu_to_le16(src->htotal);
+	dst->hskew = cpu_to_le16(src->hskew);
+	dst->vdisplay = cpu_to_le16(src->vdisplay);
+	dst->vsync_start = cpu_to_le16(src->vsync_start);
+	dst->vsync_end = cpu_to_le16(src->vsync_end);
+	dst->vtotal = cpu_to_le16(src->vtotal);
+	dst->vscan = cpu_to_le16(src->vscan);
+	dst->vrefresh = cpu_to_le32(src->vrefresh);
+	dst->flags = cpu_to_le32(flags);
+	dst->type = src->type;
+}
+
+static inline void gud_drm_to_display_mode(struct drm_display_mode *dst,
+					   const struct gud_drm_display_mode *src)
+{
+	u32 flags = le32_to_cpu(src->flags);
+
+	dst->clock = le32_to_cpu(src->clock);
+	dst->hdisplay = le16_to_cpu(src->hdisplay);
+	dst->hsync_start = le16_to_cpu(src->hsync_start);
+	dst->hsync_end = le16_to_cpu(src->hsync_end);
+	dst->htotal = le16_to_cpu(src->htotal);
+	dst->hskew = le16_to_cpu(src->hskew);
+	dst->vdisplay = le16_to_cpu(src->vdisplay);
+	dst->vsync_start = le16_to_cpu(src->vsync_start);
+	dst->vsync_end = le16_to_cpu(src->vsync_end);
+	dst->vtotal = le16_to_cpu(src->vtotal);
+	dst->vscan = le16_to_cpu(src->vscan);
+	dst->vrefresh = le32_to_cpu(src->vrefresh);
+	dst->flags = flags & ~DRM_MODE_FLAG_PIC_AR_MASK;
+	dst->type = src->type;
+
+	switch (flags & DRM_MODE_FLAG_PIC_AR_MASK) {
+	case DRM_MODE_FLAG_PIC_AR_4_3:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3;
+		break;
+	case DRM_MODE_FLAG_PIC_AR_16_9:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;
+		break;
+	case DRM_MODE_FLAG_PIC_AR_64_27:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27;
+		break;
+	case DRM_MODE_FLAG_PIC_AR_256_135:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135;
+		break;
+	default:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE;
+		break;
+	}
+
+	drm_mode_set_name(dst);
+}
+
+#endif
-- 
2.23.0


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

* [PATCH 08/10] drm: Add Generic USB Display driver
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

This adds a generic USB display driver with the intention that it can be
used with future USB interfaced low end displays/adapters. The Linux
gadget device driver will serve as the canonical device implementation.

The following DRM properties are supported:
- Plane rotation
- Connector TV properties

There is also support for backlight brightness exposed as a backlight
device.

Display modes can be made available to the host driver either as DRM
display modes or through EDID. If both are present, EDID is just passed
on to userspace.

Performance is preferred over color depth, so if the device supports
RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16.

If the device transfer buffer can't fit an uncompressed framebuffer
update, the update is split up into parts that do fit.

Optimal user experience is achieved by providing damage reports either by
setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB.

LZ4 compression is used if the device supports it.

The driver supports a one bit monochrome transfer format: R1. This is not
implemented in the gadget driver. It is added in preparation for future
monochrome e-ink displays.

The driver is MIT licensed to smooth the path for any BSD port of the
driver.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 MAINTAINERS                             |   8 +
 drivers/gpu/drm/Kconfig                 |   2 +
 drivers/gpu/drm/Makefile                |   1 +
 drivers/gpu/drm/gud/Kconfig             |  14 +
 drivers/gpu/drm/gud/Makefile            |   4 +
 drivers/gpu/drm/gud/gud_drm_connector.c | 629 ++++++++++++++++++++++++
 drivers/gpu/drm/gud/gud_drm_drv.c       | 624 +++++++++++++++++++++++
 drivers/gpu/drm/gud/gud_drm_internal.h  |  66 +++
 drivers/gpu/drm/gud/gud_drm_pipe.c      | 423 ++++++++++++++++
 include/drm/gud_drm.h                   | 356 ++++++++++++++
 10 files changed, 2127 insertions(+)
 create mode 100644 drivers/gpu/drm/gud/Kconfig
 create mode 100644 drivers/gpu/drm/gud/Makefile
 create mode 100644 drivers/gpu/drm/gud/gud_drm_connector.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_drv.c
 create mode 100644 drivers/gpu/drm/gud/gud_drm_internal.h
 create mode 100644 drivers/gpu/drm/gud/gud_drm_pipe.c
 create mode 100644 include/drm/gud_drm.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 08ae8ea76939..bb4609984e9a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5282,6 +5282,14 @@ S:	Maintained
 F:	drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c
 F:	Documentation/devicetree/bindings/display/panel/feiyang,fy07024di26a30d.txt
 
+DRM DRIVER FOR GENERIC USB DISPLAY
+M:	Noralf Trønnes <noralf@tronnes.org>
+S:	Maintained
+W:	https://github.com/notro/gud/wiki
+T:	git git://anongit.freedesktop.org/drm/drm-misc
+F:	drivers/gpu/drm/gud/
+F:	include/drm/gud_drm.h
+
 DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
 M:	Hans de Goede <hdegoede@redhat.com>
 T:	git git://anongit.freedesktop.org/drm/drm-misc
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index f6e13e18c9ca..928bbf5cdc10 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -396,6 +396,8 @@ source "drivers/gpu/drm/aspeed/Kconfig"
 
 source "drivers/gpu/drm/mcde/Kconfig"
 
+source "drivers/gpu/drm/gud/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0d17662dde0a..86af6c2da40d 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -123,3 +123,4 @@ obj-$(CONFIG_DRM_LIMA)  += lima/
 obj-$(CONFIG_DRM_PANFROST) += panfrost/
 obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
 obj-$(CONFIG_DRM_MCDE) += mcde/
+obj-y			+= gud/
diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig
new file mode 100644
index 000000000000..203d4490f1c7
--- /dev/null
+++ b/drivers/gpu/drm/gud/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config DRM_GUD
+	tristate "Generic USB Display"
+	depends on DRM && USB
+	select LZ4_COMPRESS
+	select DRM_KMS_HELPER
+	select DRM_GEM_SHMEM_HELPER
+	select DRM_BACKLIGHT_HELPER
+	help
+	  This is a DRM display driver for Generic USB Displays or display
+	  adapters.
+
+	  If M is selected the module will be called gud_drm.
diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile
new file mode 100644
index 000000000000..73ed7ef3da94
--- /dev/null
+++ b/drivers/gpu/drm/gud/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+gud_drm-objs			:= gud_drm_drv.o gud_drm_pipe.o gud_drm_connector.o
+obj-$(CONFIG_DRM_GUD)		+= gud_drm.o
diff --git a/drivers/gpu/drm/gud/gud_drm_connector.c b/drivers/gpu/drm/gud/gud_drm_connector.c
new file mode 100644
index 000000000000..d1042b563a98
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_connector.c
@@ -0,0 +1,629 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_backlight_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+struct gud_drm_connector {
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+
+	u32 flags;
+
+	/* Supported properties */
+	u16 *properties;
+	unsigned int num_properties;
+
+	/* Initial gadget tv state if applicable, applied on state reset */
+	struct drm_tv_connector_state initial_tv_state;
+
+	/*
+	 * Initial gadget backlight brightness if applicable, applied on state reset.
+	 * The value -ENODEV is used internally to signal no backlight.
+	 */
+	int initial_brightness;
+
+	/* Supported display modes in transfer format */
+	struct gud_drm_display_mode *modes;
+	unsigned int num_modes;
+
+	/* EDID */
+	void *edid;
+	size_t edid_len;
+};
+
+static inline struct gud_drm_connector *to_gud_drm_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct gud_drm_connector, connector);
+}
+
+static void gud_drm_connector_modes_clear(struct gud_drm_connector *gconn)
+{
+	kfree(gconn->modes);
+	gconn->modes = NULL;
+	gconn->num_modes = 0;
+}
+
+static int gud_drm_connector_modes_get(struct gud_drm_connector *gconn, unsigned int num_modes)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(gconn->connector.dev);
+	unsigned int index = gconn->connector.index;
+	int ret = 0;
+
+	if (!num_modes)
+		goto clear;
+
+	gud_drm_connector_modes_clear(gconn);
+
+	gconn->modes = kmalloc_array(num_modes, sizeof(*gconn->modes), GFP_KERNEL);
+	if (!gconn->modes)
+		return -ENOMEM;
+
+	gconn->num_modes = num_modes;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_MODES, index,
+			      gconn->modes, num_modes * sizeof(*gconn->modes));
+	if (ret)
+		goto clear;
+
+	return 0;
+clear:
+	gud_drm_connector_modes_clear(gconn);
+
+	return ret;
+}
+
+static void gud_drm_connector_edid_clear(struct gud_drm_connector *gconn)
+{
+	kfree(gconn->edid);
+	gconn->edid = NULL;
+	gconn->edid_len = 0;
+}
+
+static int gud_drm_connector_edid_get(struct gud_drm_connector *gconn, size_t len)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(gconn->connector.dev);
+	unsigned int index = gconn->connector.index;
+	int ret = 0;
+
+	if (!len)
+		goto clear;
+
+	gud_drm_connector_edid_clear(gconn);
+
+	gconn->edid = kmalloc(len, GFP_KERNEL);
+	if (!gconn->edid)
+		return -ENOMEM;
+
+	gconn->edid_len = len;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_EDID, index, gconn->edid, len);
+	if (ret)
+		goto clear;
+
+	return 0;
+clear:
+	gud_drm_connector_edid_clear(gconn);
+
+	return ret;
+}
+
+static int gud_drm_connector_detect_safe(struct drm_connector *connector, bool force)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+	struct gud_drm_device *gdrm = to_gud_drm_device(connector->dev);
+	struct gud_drm_req_get_connector_status req;
+	u16 num_modes, edid_len;
+	int status, ret;
+	bool changed;
+
+	if (force) {
+		ret = gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_CONNECTOR_FORCE_DETECT,
+				      connector->index, NULL, 0);
+		if (ret)
+			goto free;
+	}
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_STATUS,
+			      connector->index, &req, sizeof(req));
+	if (ret)
+		goto free;
+
+	changed = req.status & GUD_DRM_CONNECTOR_STATUS_CHANGED;
+	status = req.status & GUD_DRM_CONNECTOR_STATUS_MASK;
+	if (status > connector_status_unknown)
+		status = connector_status_unknown;
+	num_modes = le16_to_cpu(req.num_modes);
+	edid_len = le16_to_cpu(req.edid_len);
+
+	if (!num_modes && !edid_len) {
+		ret = connector_status_disconnected;
+		goto free;
+	}
+
+	if (!changed && connector->status == status &&
+	    gconn->num_modes == num_modes && gconn->edid_len == edid_len)
+		return status;
+
+	ret = gud_drm_connector_modes_get(gconn, num_modes);
+	if (ret)
+		goto free;
+
+	ret = gud_drm_connector_edid_get(gconn, edid_len);
+	if (ret)
+		goto free;
+
+	return status;
+free:
+	gud_drm_connector_modes_clear(gconn);
+	gud_drm_connector_edid_clear(gconn);
+
+	return ret < 0 ? connector_status_unknown : ret;
+}
+
+static int gud_drm_connector_detect(struct drm_connector *connector,
+				    struct drm_modeset_acquire_ctx *ctx, bool force)
+{
+	int idx, ret;
+
+	if (!drm_dev_enter(connector->dev, &idx))
+		return -ENODEV;
+
+	ret = gud_drm_connector_detect_safe(connector, force);
+
+	drm_dev_exit(idx);
+
+	return ret;
+}
+
+static int gud_drm_connector_get_modes(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+	unsigned int i, num_modes = 0;
+
+	if (!gconn->num_modes)
+		return drm_add_edid_modes(connector, gconn->edid);
+
+	for (i = 0; i < gconn->num_modes; i++) {
+		struct drm_display_mode *mode;
+
+		mode = drm_mode_create(connector->dev);
+		if (!mode)
+			goto out;
+
+		gud_drm_to_display_mode(mode, &gconn->modes[i]);
+
+		drm_mode_probed_add(connector, mode);
+		num_modes++;
+	}
+out:
+	if (gconn->edid_len)
+		drm_connector_update_edid_property(connector, gconn->edid);
+
+	return num_modes;
+}
+
+static int gud_drm_connector_atomic_check(struct drm_connector *connector,
+					  struct drm_atomic_state *state)
+{
+	struct drm_connector_state *new_state;
+	struct drm_crtc_state *new_crtc_state;
+	struct drm_connector_state *old_state;
+
+	new_state = drm_atomic_get_new_connector_state(state, connector);
+	if (!new_state->crtc)
+		return 0;
+
+	old_state = drm_atomic_get_old_connector_state(state, connector);
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
+
+	if (old_state->backlight_brightness != new_state->backlight_brightness ||
+	    old_state->tv.subconnector != new_state->tv.subconnector ||
+	    old_state->tv.margins.left != new_state->tv.margins.left ||
+	    old_state->tv.margins.right != new_state->tv.margins.right ||
+	    old_state->tv.margins.top != new_state->tv.margins.top ||
+	    old_state->tv.margins.bottom != new_state->tv.margins.bottom ||
+	    old_state->tv.mode != new_state->tv.mode ||
+	    old_state->tv.brightness != new_state->tv.brightness ||
+	    old_state->tv.contrast != new_state->tv.contrast ||
+	    old_state->tv.flicker_reduction != new_state->tv.flicker_reduction ||
+	    old_state->tv.overscan != new_state->tv.overscan ||
+	    old_state->tv.saturation != new_state->tv.saturation ||
+	    old_state->tv.hue != new_state->tv.hue)
+		new_crtc_state->connectors_changed = true;
+
+	return 0;
+}
+
+static const struct drm_connector_helper_funcs gud_drm_connector_helper_funcs = {
+	.detect_ctx = gud_drm_connector_detect,
+	.get_modes = gud_drm_connector_get_modes,
+	.atomic_check = gud_drm_connector_atomic_check,
+};
+
+static int gud_drm_connector_late_register(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+
+	if (gconn->initial_brightness < 0)
+		return 0;
+
+	return drm_backlight_register(connector);
+}
+
+static void gud_drm_connector_destroy(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+
+	drm_connector_cleanup(connector);
+	gud_drm_connector_modes_clear(gconn);
+	gud_drm_connector_edid_clear(gconn);
+	kfree(gconn->properties);
+	kfree(gconn);
+}
+
+static void gud_drm_connector_reset(struct drm_connector *connector)
+{
+	struct gud_drm_connector *gconn = to_gud_drm_connector(connector);
+
+	drm_atomic_helper_connector_reset(connector);
+	connector->state->tv = gconn->initial_tv_state;
+	/* Set margins from command line */
+	drm_atomic_helper_connector_tv_reset(connector);
+	if (gconn->initial_brightness >= 0)
+		connector->state->backlight_brightness = gconn->initial_brightness;
+}
+
+static const struct drm_connector_funcs gud_drm_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.late_register = gud_drm_connector_late_register,
+	.early_unregister = drm_backlight_unregister,
+	.destroy = gud_drm_connector_destroy,
+	.reset = gud_drm_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/*
+ * The tv.mode property is shared among the connectors and its enum names are
+ * driver specific. This means that if more than one connector uses tv.mode,
+ * the enum names has to be the same.
+ */
+static int gud_drm_connector_add_tv_mode(struct gud_drm_device *gdrm,
+					 struct drm_connector *connector, u64 val)
+{
+	unsigned int i, num_modes;
+	const char **modes;
+	size_t buf_len;
+	char *buf;
+	int ret;
+
+	num_modes = val >> GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT;
+
+	if (!num_modes)
+		return -EINVAL;
+
+	buf_len = num_modes * DRM_PROP_NAME_LEN;
+	modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL);
+	buf = kmalloc(buf_len, GFP_KERNEL);
+	if (!modes || !buf) {
+		ret = -ENOMEM;
+		goto free;
+	}
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_TV_MODE_VALUES,
+			      connector->index, buf, buf_len);
+	if (ret)
+		goto free;
+
+	for (i = 0; i < num_modes; i++)
+		modes[i] = &buf[i * DRM_PROP_NAME_LEN];
+
+	ret = drm_mode_create_tv_properties(connector->dev, num_modes, modes);
+free:
+	kfree(modes);
+	kfree(buf);
+
+	return ret;
+}
+
+static struct drm_property *
+gud_drm_connector_property_lookup(struct drm_connector *connector, u16 prop)
+{
+	struct drm_mode_config *config = &connector->dev->mode_config;
+
+	switch (prop) {
+	case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+		return config->tv_select_subconnector_property;
+	case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+		return config->tv_left_margin_property;
+	case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+		return config->tv_right_margin_property;
+	case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+		return config->tv_top_margin_property;
+	case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+		return config->tv_bottom_margin_property;
+	case GUD_DRM_PROPERTY_TV_MODE:
+		return config->tv_mode_property;
+	case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+		return config->tv_brightness_property;
+	case GUD_DRM_PROPERTY_TV_CONTRAST:
+		return config->tv_contrast_property;
+	case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+		return config->tv_flicker_reduction_property;
+	case GUD_DRM_PROPERTY_TV_OVERSCAN:
+		return config->tv_overscan_property;
+	case GUD_DRM_PROPERTY_TV_SATURATION:
+		return config->tv_saturation_property;
+	case GUD_DRM_PROPERTY_TV_HUE:
+		return config->tv_hue_property;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+}
+
+static unsigned int *gud_drm_connector_tv_state_val(u16 prop, struct drm_tv_connector_state *state)
+{
+	switch (prop) {
+	case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+		return &state->subconnector;
+	case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+		return &state->margins.left;
+	case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+		return &state->margins.right;
+	case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+		return &state->margins.top;
+	case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+		return &state->margins.bottom;
+	case GUD_DRM_PROPERTY_TV_MODE:
+		return &state->mode;
+	case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+		return &state->brightness;
+	case GUD_DRM_PROPERTY_TV_CONTRAST:
+		return &state->contrast;
+	case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+		return &state->flicker_reduction;
+	case GUD_DRM_PROPERTY_TV_OVERSCAN:
+		return &state->overscan;
+	case GUD_DRM_PROPERTY_TV_SATURATION:
+		return &state->saturation;
+	case GUD_DRM_PROPERTY_TV_HUE:
+		return &state->hue;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+}
+
+static int gud_drm_connector_add_properties(struct gud_drm_device *gdrm,
+					    struct gud_drm_connector *gconn,
+					    unsigned int num_properties)
+{
+	struct drm_device *drm = &gdrm->drm;
+	struct drm_connector *connector = &gconn->connector;
+	struct gud_drm_property *properties;
+	bool need_tv_props = false;
+	unsigned int i;
+	int ret;
+
+	gconn->properties = kcalloc(num_properties, sizeof(*gconn->properties), GFP_KERNEL);
+	if (!gconn->properties)
+		return -ENOMEM;
+
+	properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL);
+	if (!properties)
+		return -ENOMEM;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR_PROPERTIES, connector->index,
+			      properties, num_properties * sizeof(*properties));
+	if (ret)
+		goto out;
+
+	for (i = 0; i < num_properties; i++) {
+		u16 prop = le16_to_cpu(properties[i].prop);
+		u64 val = le64_to_cpu(properties[i].val);
+
+		drm_dbg(drm, "property: %u = %llu(0x%llx)\n", prop, val, val);
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+			ret = drm_mode_create_tv_margin_properties(drm);
+			if (ret)
+				goto out;
+			break;
+		case GUD_DRM_PROPERTY_TV_MODE:
+			ret = gud_drm_connector_add_tv_mode(gdrm, connector, val);
+			if (ret)
+				goto out;
+			break;
+		case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_CONTRAST:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_OVERSCAN:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_SATURATION:
+			fallthrough;
+		case GUD_DRM_PROPERTY_TV_HUE:
+			need_tv_props = true;
+			break;
+		case GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS:
+			if (val > 100) {
+				ret = -EINVAL;
+				goto out;
+			}
+			gconn->initial_brightness = val;
+			break;
+		default:
+			/* New ones might show up in future devices, skip those we don't know. */
+			drm_dbg(drm, "Unknown property: %u\n", prop);
+			continue;
+		}
+
+		gconn->properties[gconn->num_properties++] = prop;
+	}
+
+	if (!gconn->num_properties)
+		goto out;
+
+	if (need_tv_props) {
+		/* This is a no-op if already added. */
+		ret = drm_mode_create_tv_properties(drm, 0, NULL);
+		if (ret)
+			goto out;
+	}
+
+	for (i = 0; i < num_properties; i++) {
+		u16 prop = le16_to_cpu(properties[i].prop);
+		u64 val = le64_to_cpu(properties[i].val);
+		struct drm_property *property;
+		unsigned int *state_val;
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS:
+			/* not a DRM property */
+			continue;
+		case GUD_DRM_PROPERTY_TV_MODE:
+			val = val & (BIT(GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT) - 1);
+			break;
+		}
+
+		property = gud_drm_connector_property_lookup(connector, prop);
+		if (IS_ERR(property))
+			continue;
+
+		state_val = gud_drm_connector_tv_state_val(prop, &gconn->initial_tv_state);
+		if (IS_ERR(state_val))
+			continue;
+
+		*state_val = val;
+		drm_object_attach_property(&connector->base, property, 0);
+	}
+out:
+	kfree(properties);
+
+	return ret;
+}
+
+int gud_drm_connector_fill_properties(struct drm_connector *connector,
+				      struct drm_connector_state *connector_state,
+				      struct gud_drm_property *properties)
+{
+	struct gud_drm_connector *gconn;
+	unsigned int i;
+
+	gconn = to_gud_drm_connector(connector);
+
+	/* Only interested in the count? */
+	if (!connector_state)
+		return gconn->num_properties;
+
+	for (i = 0; i < gconn->num_properties; i++) {
+		u16 prop = gconn->properties[i];
+		u64 val;
+
+		if (prop == GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS) {
+			val = connector_state->backlight_brightness;
+		} else {
+			unsigned int *state_val;
+
+			state_val = gud_drm_connector_tv_state_val(prop, &connector_state->tv);
+			if (WARN_ON_ONCE(IS_ERR(state_val)))
+				return PTR_ERR(state_val);
+
+			val = *state_val;
+		}
+
+		properties[i].prop = cpu_to_le16(prop);
+		properties[i].val = cpu_to_le64(val);
+	}
+
+	return gconn->num_properties;
+}
+
+int gud_drm_connector_create(struct gud_drm_device *gdrm, unsigned int index)
+{
+	struct gud_drm_req_get_connector desc;
+	struct drm_device *drm = &gdrm->drm;
+	struct gud_drm_connector *gconn;
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+	int ret;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_CONNECTOR, index, &desc, sizeof(desc));
+	if (ret)
+		return ret;
+
+	drm_dbg(drm, "index=%u type=%u flags=0x%x num_properties=%u\n", index,
+		desc.connector_type, le32_to_cpu(desc.flags), desc.num_properties);
+
+	/* REVISIT: This needs to be updated as new types are added */
+	if (desc.connector_type > DRM_MODE_CONNECTOR_SPI)
+		return -EINVAL;
+
+	gconn = kzalloc(sizeof(*gconn), GFP_KERNEL);
+	if (!gconn)
+		return -ENOMEM;
+
+	gconn->initial_brightness = -ENODEV;
+	gconn->flags = le32_to_cpu(desc.flags);
+	connector = &gconn->connector;
+
+	drm_connector_helper_add(connector, &gud_drm_connector_helper_funcs);
+	ret = drm_connector_init(drm, connector, &gud_drm_connector_funcs, desc.connector_type);
+	if (ret) {
+		kfree(connector);
+		return ret;
+	}
+
+	if (WARN_ON(connector->index != index))
+		return -EINVAL;
+
+	if (gconn->flags & GUD_DRM_CONNECTOR_FLAGS_POLL)
+		connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT);
+
+	if (desc.num_properties) {
+		ret = gud_drm_connector_add_properties(gdrm, gconn, desc.num_properties);
+		if (ret) {
+			dev_err(drm->dev, "Failed to add connector/%u properties\n", index);
+			return ret;
+		}
+	}
+
+	/* The first connector is attached to the existing simple pipe encoder */
+	if (!connector->index) {
+		encoder = &gdrm->pipe.encoder;
+	} else {
+		encoder = &gconn->encoder;
+
+		ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
+		if (ret)
+			return ret;
+
+		encoder->possible_crtcs = 1;
+	}
+
+	return drm_connector_attach_encoder(connector, encoder);
+}
diff --git a/drivers/gpu/drm/gud/gud_drm_drv.c b/drivers/gpu/drm/gud/gud_drm_drv.c
new file mode 100644
index 000000000000..4e1a0115ca6c
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_drv.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/lz4.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/string_helpers.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+/* Only used internally */
+static const struct drm_format_info gud_drm_format_r1 = {
+	.format = GUD_DRM_FORMAT_R1,
+	.num_planes = 1,
+	.char_per_block = { 1, 0, 0 },
+	.block_w = { 8, 0, 0 },
+	.block_h = { 1, 0, 0 },
+	.hsub = 1,
+	.vsub = 1,
+};
+
+static int gud_get_vendor_descriptor(struct usb_interface *interface,
+				     struct gud_drm_display_descriptor *desc)
+{
+	u8 ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
+	struct usb_device *usb = interface_to_usbdev(interface);
+	void *buf;
+	int ret;
+
+	buf = kmalloc(sizeof(*desc), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(usb, usb_rcvctrlpipe(usb, 0),
+			      USB_REQ_GET_DESCRIPTOR,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			      GUD_DRM_USB_DT_DISPLAY << 8, ifnum, buf, sizeof(*desc),
+			      USB_CTRL_GET_TIMEOUT);
+
+	memcpy(desc, buf, sizeof(*desc));
+	kfree(buf);
+
+	if (ret < 0)
+		return ret;
+
+	if (ret != sizeof(*desc) || desc->bLength != sizeof(*desc) ||
+	    desc->bDescriptorType != GUD_DRM_USB_DT_DISPLAY)
+		return -ENODATA;
+
+	DRM_DEV_DEBUG_DRIVER(&interface->dev,
+			     "Version=%u Compression=0x%x NumFormats=%u NumConnectors=%u MaxBufferSizeOrder=%u\n",
+			     desc->bVersion, desc->bCompression, desc->bNumFormats,
+			     desc->bNumConnectors, desc->bMaxBufferSizeOrder);
+
+	if (desc->bVersion != 1 || !desc->bNumFormats || !desc->bNumConnectors ||
+	    !desc->bMaxBufferSizeOrder || !desc->dwMaxWidth || !desc->dwMaxHeight ||
+	    le32_to_cpu(desc->dwMinWidth) > le32_to_cpu(desc->dwMaxWidth) ||
+	    le32_to_cpu(desc->dwMinHeight) > le32_to_cpu(desc->dwMaxHeight))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int gud_usb_control_msg(struct gud_drm_device *gdrm, bool in,
+			       u8 request, u16 value, void *buf, size_t len)
+{
+	u8 requesttype = USB_TYPE_VENDOR | USB_RECIP_INTERFACE;
+	unsigned int pipe;
+	int idx, ret;
+
+	if (!drm_dev_enter(&gdrm->drm, &idx))
+		return -ENODEV;
+
+	if (in) {
+		pipe = usb_rcvctrlpipe(gdrm->usb, 0);
+		requesttype |= USB_DIR_IN;
+	} else {
+		pipe = usb_sndctrlpipe(gdrm->usb, 0);
+		requesttype |= USB_DIR_OUT;
+	}
+
+	ret = usb_control_msg(gdrm->usb, pipe, request, requesttype, value,
+			      gdrm->ifnum, buf, len, USB_CTRL_GET_TIMEOUT);
+	if (ret >= 0) {
+		if (ret != len)
+			ret = -EIO;
+		else
+			ret = 0;
+	}
+
+	drm_dev_exit(idx);
+
+	return ret;
+}
+
+static int gud_usb_get_status(struct gud_drm_device *gdrm)
+{
+	struct gud_drm_req_get_status *status = gdrm->status_buf;
+	int ret, status_retries = 2000 / 5; /* maximum wait ~2 seconds */
+	unsigned long delay = 500;
+
+	/*
+	 * Poll due to lack of data/status stage control on the gadget side.
+	 *
+	 * If we did not use polling and gave up here after waiting 2 seconds,
+	 * the worker in the gadget would finally get to queuing up the status
+	 * respons, but by that time the host has moved on. The gadget side
+	 * (at least dwc2) would now be left in a non-recoverable state.
+	 *
+	 * Worst case commit timeout in DRM can be tens of seconds (wait for
+	 * various _done completions).
+	 */
+	while (status_retries--) {
+		ret = gud_usb_control_msg(gdrm, true, USB_REQ_GET_STATUS, 0,
+					  status, sizeof(*status));
+		if (ret)
+			return ret;
+
+		if (!(status->flags & GUD_DRM_STATUS_PENDING))
+			return -status->errno;
+
+		usleep_range(delay, delay + 1000);
+
+		if (delay < 4500)
+			delay += 1000;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int gud_usb_transfer(struct gud_drm_device *gdrm, bool in, u8 request,
+			    u16 index, void *buf, size_t len)
+{
+	int ret;
+
+	drm_dbg(&gdrm->drm, "%s: request=0x%x index=%u len=%zu\n",
+		in ? "get" : "set", request, index, len);
+
+	if (len > GUD_DRM_MAX_TRANSFER_SIZE)
+		return -ENOMEM;
+
+	mutex_lock(&gdrm->ctrl_lock);
+
+	if (!in && buf)
+		memcpy(gdrm->ctrl_msg_buf, buf, len);
+
+	ret = gud_usb_control_msg(gdrm, in, request, index, gdrm->ctrl_msg_buf, len);
+
+	/*
+	 * OUT transfers are processed in a worker on the gadget side after
+	 * reception so we always need to check status. IN transfers are
+	 * processed in the interrupt handler and will halt on error letting us
+	 * know something went wrong.
+	 */
+	if (ret || !in) {
+		ret = gud_usb_get_status(gdrm);
+		if (ret)
+			goto unlock;
+	}
+
+	if (in && buf)
+		memcpy(buf, gdrm->ctrl_msg_buf, len);
+unlock:
+	mutex_unlock(&gdrm->ctrl_lock);
+
+	if (ret) {
+		drm_dbg(&gdrm->drm, "ret=%d\n", ret);
+		gdrm->stats_num_errors++;
+	}
+
+	return ret;
+}
+
+int gud_drm_usb_get(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len)
+{
+	return gud_usb_transfer(gdrm, true, request, index, buf, len);
+}
+
+int gud_drm_usb_set(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len)
+{
+	return gud_usb_transfer(gdrm, false, request, index, buf, len);
+}
+
+int gud_drm_usb_write8(struct gud_drm_device *gdrm, u8 request, u8 val)
+{
+	return gud_drm_usb_set(gdrm, request, 0, &val, sizeof(val));
+}
+
+static int gud_drm_usb_read32(struct gud_drm_device *gdrm, u8 request,
+			      u32 *vals, unsigned int num_vals)
+{
+	unsigned int i;
+	int ret;
+
+	ret = gud_drm_usb_get(gdrm, request, 0, vals, num_vals * sizeof(*vals));
+	if (ret)
+		return ret;
+
+	for (i = 0; i < num_vals; i++)
+		vals[i] = le32_to_cpu((__le32)vals[i]);
+
+	return 0;
+}
+
+static int gud_drm_get_properties(struct gud_drm_device *gdrm, unsigned int num_properties)
+{
+	struct gud_drm_property *properties;
+	unsigned int i;
+	int ret;
+
+	if (!num_properties)
+		return 0;
+
+	gdrm->properties = kcalloc(num_properties, sizeof(*gdrm->properties), GFP_KERNEL);
+	if (!gdrm->properties)
+		return -ENOMEM;
+
+	properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL);
+	if (!properties)
+		return -ENOMEM;
+
+	ret = gud_drm_usb_get(gdrm, GUD_DRM_USB_REQ_GET_PROPERTIES, 0,
+			      properties, num_properties * sizeof(*properties));
+	if (ret)
+		goto out;
+
+	for (i = 0; i < num_properties; i++) {
+		u16 prop = le16_to_cpu(properties[i].prop);
+		u64 val = le64_to_cpu(properties[i].val);
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_ROTATION:
+			ret = drm_plane_create_rotation_property(&gdrm->pipe.plane,
+								 DRM_MODE_ROTATE_0, val);
+			break;
+		default:
+			/* New ones might show up in future devices, skip those we don't know. */
+			drm_dbg(&gdrm->drm, "Unknown property: %u\n", prop);
+			continue;
+		}
+
+		if (ret)
+			goto out;
+
+		gdrm->properties[gdrm->num_properties++] = prop;
+	}
+out:
+	kfree(properties);
+
+	return ret;
+}
+
+static void gud_drm_driver_release(struct drm_device *drm)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(drm);
+
+	drm_dbg(&gdrm->drm, "%s:\n", __func__);
+
+	drm_mode_config_cleanup(drm);
+	drm_dev_fini(drm);
+
+	kfree(gdrm->properties);
+	vfree(gdrm->compress_buf);
+	kfree(gdrm->bulk_buf);
+	kfree(gdrm->ctrl_msg_buf);
+
+	mutex_destroy(&gdrm->ctrl_lock);
+	mutex_destroy(&gdrm->damage_lock);
+
+	kfree(gdrm);
+}
+
+static struct drm_gem_object *
+gud_drm_driver_gem_create_object(struct drm_device *dev, size_t size)
+{
+	struct drm_gem_shmem_object *shmem;
+
+	shmem = kzalloc(sizeof(*shmem), GFP_KERNEL);
+	if (!shmem)
+		return NULL;
+
+	/*
+	 * This doesn't make a difference on x86, but on ARM (pi4) it was
+	 * necessary to avoid black lines all over and it made it possible to
+	 * compress directly from the framebuffer without performance drop.
+	 */
+	shmem->map_cached = true;
+
+	return &shmem->base;
+}
+
+static int gud_drm_stats_debugfs(struct seq_file *m, void *data)
+{
+	struct drm_info_node *node = m->private;
+	struct gud_drm_device *gdrm = to_gud_drm_device(node->minor->dev);
+	char buf[10];
+
+	string_get_size(gdrm->bulk_len, 1, STRING_UNITS_2, buf, sizeof(buf));
+	seq_printf(m, "Max buffer size: %s\n", buf);
+	seq_printf(m, "Number of errors:  %u\n", gdrm->stats_num_errors);
+
+	seq_puts(m, "Compression:      ");
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4)
+		seq_puts(m, " lz4");
+	seq_puts(m, "\n");
+
+	if (gdrm->compression) {
+		u64 remainder;
+		u64 ratio = div64_u64_rem(gdrm->stats_length, gdrm->stats_actual_length,
+					  &remainder);
+		u64 ratio_frac = div64_u64(remainder * 10, gdrm->stats_actual_length);
+
+		seq_printf(m, "Compression ratio: %llu.%llu\n", ratio, ratio_frac);
+	}
+
+	return 0;
+}
+
+static const struct drm_info_list gud_drm_debugfs_list[] = {
+	{ "stats", gud_drm_stats_debugfs, 0, NULL },
+};
+
+static int gud_drm_driver_debugfs_init(struct drm_minor *minor)
+{
+	return drm_debugfs_create_files(gud_drm_debugfs_list,
+					ARRAY_SIZE(gud_drm_debugfs_list),
+					minor->debugfs_root, minor);
+}
+
+static const struct drm_simple_display_pipe_funcs gud_drm_pipe_funcs = {
+	.check      = gud_drm_pipe_check,
+	.update	    = gud_drm_pipe_update,
+	.prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
+};
+
+static const struct drm_mode_config_funcs gud_drm_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create_with_dirty,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static const uint64_t gud_drm_pipe_modifiers[] = {
+	DRM_FORMAT_MOD_LINEAR,
+	DRM_FORMAT_MOD_INVALID
+};
+
+DEFINE_DRM_GEM_FOPS(gud_drm_fops);
+
+static struct drm_driver gud_drm_driver = {
+	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	.fops			= &gud_drm_fops,
+	.release		= gud_drm_driver_release,
+	.gem_create_object	= gud_drm_driver_gem_create_object,
+	DRM_GEM_SHMEM_DRIVER_OPS,
+	.debugfs_init		= gud_drm_driver_debugfs_init,
+
+	.name			= "gud_drm",
+	.desc			= "Generic USB Display",
+	.date			= "20200422",
+	.major			= 1,
+	.minor			= 0,
+};
+
+static int gud_drm_probe(struct usb_interface *interface,
+			 const struct usb_device_id *id)
+{
+	const struct drm_format_info *xrgb8888_emulation_format = NULL;
+	u32 *formats, *formats_dev, num_connectors, num_formats = 0;
+	bool rgb565_supported = false, rgb8888_supported = false;
+	struct usb_device *usb = interface_to_usbdev(interface);
+	struct usb_endpoint_descriptor *bulk_out;
+	struct gud_drm_display_descriptor desc;
+	struct device *dev = &interface->dev;
+	struct gud_drm_device *gdrm;
+	struct drm_device *drm;
+	size_t max_buffer_size;
+	int ret, i;
+
+	ret = usb_find_bulk_out_endpoint(interface->cur_altsetting, &bulk_out);
+	if (ret)
+		return ret;
+
+	ret = gud_get_vendor_descriptor(interface, &desc);
+	if (ret) {
+		DRM_DEV_DEBUG_DRIVER(dev, "Not a display interface: ret=%d\n", ret);
+		return -ENODEV;
+	}
+
+	num_connectors = desc.bNumConnectors;
+	max_buffer_size = 1 << desc.bMaxBufferSizeOrder;
+
+	gdrm = kzalloc(sizeof(*gdrm), GFP_KERNEL);
+	if (!gdrm)
+		return -ENOMEM;
+
+	gdrm->usb = usb;
+	gdrm->ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
+	gdrm->compression = desc.bCompression & GUD_DRM_COMPRESSION_LZ4;
+
+	drm = &gdrm->drm;
+	ret = devm_drm_dev_init(dev, drm, &gud_drm_driver);
+	if (ret) {
+		kfree(gdrm);
+		return ret;
+	}
+
+	drm_mode_config_init(drm);
+	drm->mode_config.funcs = &gud_drm_mode_config_funcs;
+
+	mutex_init(&gdrm->ctrl_lock);
+	mutex_init(&gdrm->damage_lock);
+	INIT_WORK(&gdrm->work, gud_drm_work);
+	gud_drm_clear_damage(gdrm);
+
+	/*
+	 * devm_kmalloc() places struct devres at the beginning of the buffer it
+	 * allocates. This can waste a lot of memory when allocating
+	 * power-of-two sized buffers. Asking for 4k would actually allocate 8k.
+	 */
+
+	gdrm->ctrl_msg_buf = kmalloc(GUD_DRM_MAX_TRANSFER_SIZE, GFP_KERNEL);
+	gdrm->status_buf = devm_kzalloc(dev, sizeof(struct gud_drm_req_get_status), GFP_KERNEL);
+	if (!gdrm->ctrl_msg_buf || !gdrm->status_buf)
+		return -ENOMEM;
+retry:
+	gdrm->bulk_buf = kmalloc(max_buffer_size, GFP_KERNEL);
+	if (!gdrm->bulk_buf) {
+		max_buffer_size /= 2;
+		if (max_buffer_size < SZ_2M) { /* Give up if we can't do 1024x768 RGB565 */
+			return -ENOMEM;
+		}
+		goto retry;
+	}
+
+	gdrm->bulk_pipe = usb_sndbulkpipe(gdrm->usb, usb_endpoint_num(bulk_out));
+	gdrm->bulk_len = max_buffer_size;
+
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4) {
+		gdrm->lz4_comp_mem = devm_kmalloc(dev, LZ4_MEM_COMPRESS, GFP_KERNEL);
+		if (!gdrm->lz4_comp_mem)
+			return -ENOMEM;
+
+		gdrm->compress_buf = vmalloc(gdrm->bulk_len);
+		if (!gdrm->compress_buf)
+			return -ENOMEM;
+	}
+
+	drm->mode_config.min_width = le32_to_cpu(desc.dwMinWidth);
+	drm->mode_config.max_width = le32_to_cpu(desc.dwMaxWidth);
+	drm->mode_config.min_height = le32_to_cpu(desc.dwMinHeight);
+	drm->mode_config.max_height = le32_to_cpu(desc.dwMaxHeight);
+
+	formats_dev = devm_kmalloc_array(dev, desc.bNumFormats, sizeof(u32), GFP_KERNEL);
+	/* Add room for emulated XRGB8888 */
+	formats = devm_kmalloc_array(dev, desc.bNumFormats + 1, sizeof(u32), GFP_KERNEL);
+	if (!formats_dev || !formats)
+		return -ENOMEM;
+
+	ret = gud_drm_usb_read32(gdrm, GUD_DRM_USB_REQ_GET_FORMATS, formats_dev, desc.bNumFormats);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < desc.bNumFormats; i++) {
+		const struct drm_format_info *fmt_info;
+		u32 format = formats_dev[i];
+
+		if (format == GUD_DRM_FORMAT_R1) {
+			fmt_info = &gud_drm_format_r1;
+		} else {
+			/* This will trigger a WARN for unknown formats... */
+			fmt_info = drm_format_info(format);
+			if (!fmt_info) {
+				drm_dbg(drm, "Unknown format: 0x%x\n", format);
+				continue;
+			}
+		}
+
+		switch (format) {
+		case DRM_FORMAT_XRGB8888:
+			fallthrough;
+		case DRM_FORMAT_ARGB8888:
+			rgb8888_supported = true;
+			break;
+		case DRM_FORMAT_RGB888:
+			fallthrough;
+		case DRM_FORMAT_BGR888:
+			drm_dbg(drm, "24-bit formats are not supported.\n");
+			continue;
+		case DRM_FORMAT_RGB565:
+			rgb565_supported = true;
+			if (!xrgb8888_emulation_format)
+				xrgb8888_emulation_format = fmt_info;
+			break;
+		case GUD_DRM_FORMAT_R1:
+			if (!xrgb8888_emulation_format)
+				xrgb8888_emulation_format = fmt_info;
+			/* Internal, not for userspace */
+			continue;
+		}
+
+		formats[num_formats++] = format;
+	}
+
+	if (!num_formats && !xrgb8888_emulation_format) {
+		dev_err(dev, "No supported formats found\n");
+		return -ENOENT;
+	}
+
+	/* Prefer speed over color depth */
+	if (rgb565_supported)
+		drm->mode_config.preferred_depth = 16;
+
+	if (!rgb8888_supported && xrgb8888_emulation_format) {
+		gdrm->xrgb8888_emulation_format = xrgb8888_emulation_format;
+		formats[num_formats++] = DRM_FORMAT_XRGB8888;
+	}
+
+	ret = drm_simple_display_pipe_init(drm, &gdrm->pipe, &gud_drm_pipe_funcs,
+					   formats, num_formats,
+					   gud_drm_pipe_modifiers, NULL);
+	if (ret)
+		return ret;
+
+	devm_kfree(dev, formats);
+	devm_kfree(dev, formats_dev);
+
+	ret = gud_drm_get_properties(gdrm, desc.bNumProperties);
+	if (ret)
+		return ret;
+
+	drm_plane_enable_fb_damage_clips(&gdrm->pipe.plane);
+
+	for (i = 0; i < num_connectors; i++) {
+		ret = gud_drm_connector_create(gdrm, i);
+		if (ret)
+			return ret;
+	}
+
+	drm_mode_config_reset(drm);
+
+	usb_set_intfdata(interface, gdrm);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		return ret;
+
+	drm_kms_helper_poll_init(drm);
+
+	drm_fbdev_generic_setup(drm, 16);
+
+	return 0;
+}
+
+static void gud_drm_disconnect(struct usb_interface *interface)
+{
+	struct gud_drm_device *gdrm = usb_get_intfdata(interface);
+	struct drm_device *drm = &gdrm->drm;
+
+	drm_dbg(drm, "%s:\n", __func__);
+
+	drm_kms_helper_poll_fini(drm);
+	drm_dev_unplug(drm);
+	drm_atomic_helper_shutdown(drm);
+}
+
+static int gud_drm_suspend(struct usb_interface *interface, pm_message_t message)
+{
+	struct gud_drm_device *gdrm = usb_get_intfdata(interface);
+
+	return drm_mode_config_helper_suspend(&gdrm->drm);
+}
+
+static int gud_drm_resume(struct usb_interface *interface)
+{
+	struct gud_drm_device *gdrm = usb_get_intfdata(interface);
+
+	drm_mode_config_helper_resume(&gdrm->drm);
+
+	return 0;
+}
+
+static const struct usb_device_id gud_drm_table[] = {
+	/*
+	 * FIXME:
+	 * Apply for a proper pid: https://github.com/openmoko/openmoko-usb-oui
+	 */
+	{ USB_DEVICE_INTERFACE_CLASS(0x1d50, 0x6150, USB_CLASS_VENDOR_SPEC) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, gud_drm_table);
+
+static struct usb_driver gud_drm_usb_driver = {
+	.name		= "gud_drm",
+	.probe		= gud_drm_probe,
+	.disconnect	= gud_drm_disconnect,
+	.id_table	= gud_drm_table,
+	.suspend	= gud_drm_suspend,
+	.resume		= gud_drm_resume,
+	.reset_resume	= gud_drm_resume,
+};
+
+module_usb_driver(gud_drm_usb_driver);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/gud/gud_drm_internal.h b/drivers/gpu/drm/gud/gud_drm_internal.h
new file mode 100644
index 000000000000..30d86bb0389d
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_internal.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef __LINUX_GUD_DRM_INTERNAL_H
+#define __LINUX_GUD_DRM_INTERNAL_H
+
+#include <linux/workqueue.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+
+#include <drm/drm_simple_kms_helper.h>
+
+struct gud_drm_device {
+	struct drm_device drm;
+	struct drm_simple_display_pipe pipe;
+	struct work_struct work;
+	struct usb_device *usb;
+	u8 ifnum;
+	const struct drm_format_info *xrgb8888_emulation_format;
+
+	u16 *properties;
+	unsigned int num_properties;
+
+	unsigned int bulk_pipe;
+	void *bulk_buf;
+	size_t bulk_len;
+
+	u8 compression;
+	void *lz4_comp_mem;
+	void *compress_buf;
+
+	u64 stats_length;
+	u64 stats_actual_length;
+	unsigned int stats_num_errors;
+
+	struct mutex ctrl_lock; /* Serialize req and status transfers */
+	void *ctrl_msg_buf;
+	void *status_buf;
+
+	struct mutex damage_lock; /* Protects the following members: */
+	struct drm_framebuffer *fb;
+	struct drm_rect damage;
+};
+
+static inline struct gud_drm_device *to_gud_drm_device(struct drm_device *drm)
+{
+	return container_of(drm, struct gud_drm_device, drm);
+}
+
+int gud_drm_usb_get(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len);
+int gud_drm_usb_set(struct gud_drm_device *gdrm, u8 request, u16 index, void *buf, size_t len);
+int gud_drm_usb_write8(struct gud_drm_device *gdrm, u8 request, u8 val);
+
+void gud_drm_clear_damage(struct gud_drm_device *gdrm);
+void gud_drm_work(struct work_struct *work);
+int gud_drm_pipe_check(struct drm_simple_display_pipe *pipe,
+		       struct drm_plane_state *new_plane_state,
+		       struct drm_crtc_state *new_crtc_state);
+void gud_drm_pipe_update(struct drm_simple_display_pipe *pipe,
+			 struct drm_plane_state *old_state);
+
+int gud_drm_connector_fill_properties(struct drm_connector *connector,
+				      struct drm_connector_state *connector_state,
+				      struct gud_drm_property *properties);
+int gud_drm_connector_create(struct gud_drm_device *gdrm, unsigned int index);
+
+#endif
diff --git a/drivers/gpu/drm/gud/gud_drm_pipe.c b/drivers/gpu/drm/gud/gud_drm_pipe.c
new file mode 100644
index 000000000000..51cf9b55598f
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_pipe.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/lz4.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_format_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_rect.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+static bool gud_drm_is_big_endian(void)
+{
+#if defined(__BIG_ENDIAN)
+	return true;
+#else
+	return false;
+#endif
+}
+
+static size_t gud_drm_xrgb8888_to_r124(u8 *dst, const struct drm_format_info *format,
+				       void *src, struct drm_framebuffer *fb,
+				       struct drm_rect *rect)
+{
+	unsigned int block_width = drm_format_info_block_width(format, 0);
+	unsigned int bits_per_pixel = 8 / block_width;
+	unsigned int x, y, width, height;
+	u8 *p, *block = dst; /* Assign to silence compiler warning */
+	size_t len;
+	void *buf;
+
+	WARN_ON_ONCE(format->char_per_block[0] != 1);
+
+	/* Start on a byte boundary */
+	rect->x1 = ALIGN_DOWN(rect->x1, block_width);
+	width = drm_rect_width(rect);
+	height = drm_rect_height(rect);
+	len = drm_format_info_min_pitch(format, 0, width) * height;
+
+	buf = kmalloc(width * height, GFP_KERNEL);
+	if (!buf)
+		return len; /* To keep logic simple, just transmit garbage */
+
+	drm_fb_xrgb8888_to_gray8(buf, src, fb, rect);
+
+	p = buf;
+	for (y = 0; y < drm_rect_height(rect); y++) {
+		for (x = 0; x < drm_rect_width(rect); x++) {
+			if (!(x % block_width)) {
+				block = dst++;
+				*block = 0;
+			}
+
+			*block <<= bits_per_pixel;
+			*block |= (*p++) >> (8 - bits_per_pixel);
+		}
+	}
+
+	kfree(buf);
+
+	return len;
+}
+
+static int gud_drm_fb_flush(struct gud_drm_device *gdrm, struct drm_framebuffer *fb,
+			    const struct drm_format_info *format, struct drm_rect *rect)
+{
+	struct dma_buf_attachment *import_attach = fb->obj[0]->import_attach;
+	struct gud_drm_req_set_buffer req;
+	size_t trlen, len;
+	int actual_length;
+	void *vaddr, *buf;
+	int ret = 0;
+
+	drm_dbg(&gdrm->drm, "Flushing [FB:%d] " DRM_RECT_FMT " imported=%s\n",
+		fb->base.id, DRM_RECT_ARG(rect), import_attach ? "yes" : "no");
+
+	len = drm_format_info_min_pitch(format, 0, drm_rect_width(rect)) * drm_rect_height(rect);
+	if (len > gdrm->bulk_len)
+		return -E2BIG;
+
+	vaddr = drm_gem_shmem_vmap(fb->obj[0]);
+	if (!vaddr)
+		return -ENOMEM;
+
+	if (import_attach) {
+		ret = dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE);
+		if (ret)
+			goto vunmap;
+	}
+
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4)
+		buf = gdrm->compress_buf;
+	else
+		buf = gdrm->bulk_buf;
+
+	/*
+	 * Imported buffers are assumed to be write-combined and thus uncached
+	 * with slow reads (at least on ARM).
+	 */
+	if (format != fb->format) {
+		if (format->format == GUD_DRM_FORMAT_R1)
+			len = gud_drm_xrgb8888_to_r124(buf, format, vaddr, fb, rect);
+		else if (format->format == DRM_FORMAT_RGB565)
+			drm_fb_xrgb8888_to_rgb565(buf, vaddr, fb, rect, gud_drm_is_big_endian());
+	} else if (gud_drm_is_big_endian() && format->cpp[0] > 1) {
+		drm_fb_swab(buf, vaddr, fb, rect, !import_attach);
+	} else if (!import_attach && gdrm->compression && len == fb->height * fb->pitches[0]) {
+		/* can compress directly from the framebuffer */
+		buf = vaddr;
+	} else {
+		drm_fb_memcpy(buf, vaddr, fb, rect);
+	}
+
+	req.x = cpu_to_le32(rect->x1);
+	req.y = cpu_to_le32(rect->y1);
+	req.width = cpu_to_le32(drm_rect_width(rect));
+	req.height = cpu_to_le32(drm_rect_height(rect));
+	req.length = cpu_to_le32(len);
+
+	if (gdrm->compression & GUD_DRM_COMPRESSION_LZ4) {
+		ret = LZ4_compress_default(buf, gdrm->bulk_buf, len, len, gdrm->lz4_comp_mem);
+		if (ret > 0)
+			req.compression = GUD_DRM_COMPRESSION_LZ4;
+	}
+
+	trlen = len;
+
+	if (ret > 0) {
+		req.compressed_length = cpu_to_le32(ret);
+		trlen = ret;
+	} else if (buf == gdrm->compress_buf) {
+		/*
+		 * Compression failed (buffer didn't compress well).
+		 * compress_buf is vmalloc'ed so we need to copy.
+		 */
+		memcpy(gdrm->bulk_buf, gdrm->compress_buf, len);
+	}
+
+	if (import_attach)
+		dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE);
+
+	gdrm->stats_length += len;
+	/* Did it wrap around? */
+	if (gdrm->stats_length <= len && gdrm->stats_actual_length) {
+		gdrm->stats_length = len;
+		gdrm->stats_actual_length = 0;
+	}
+	gdrm->stats_actual_length += trlen;
+
+	/*
+	 * This will wait if decompress/copy from the previous flush is still in
+	 * process on the gadget side.
+	 */
+	ret = gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_BUFFER, 0, &req, sizeof(req));
+	if (ret)
+		goto vunmap;
+
+	ret = usb_bulk_msg(gdrm->usb, gdrm->bulk_pipe, gdrm->bulk_buf, trlen,
+			   &actual_length, msecs_to_jiffies(3000));
+	if (!ret && trlen != actual_length)
+		ret = -EIO;
+	if (ret)
+		gdrm->stats_num_errors++;
+vunmap:
+	drm_gem_shmem_vunmap(fb->obj[0], vaddr);
+
+	return ret;
+}
+
+void gud_drm_clear_damage(struct gud_drm_device *gdrm)
+{
+	gdrm->damage.x1 = INT_MAX;
+	gdrm->damage.y1 = INT_MAX;
+	gdrm->damage.x2 = 0;
+	gdrm->damage.y2 = 0;
+}
+
+void gud_drm_work(struct work_struct *work)
+{
+	struct gud_drm_device *gdrm = container_of(work, struct gud_drm_device, work);
+	const struct drm_format_info *format;
+	struct drm_framebuffer *fb;
+	struct drm_rect damage;
+	unsigned int i, lines;
+	int idx, ret = 0;
+	size_t pitch;
+
+	if (!drm_dev_enter(&gdrm->drm, &idx))
+		return;
+
+	mutex_lock(&gdrm->damage_lock);
+	fb = gdrm->fb;
+	gdrm->fb = NULL;
+	damage = gdrm->damage;
+	gud_drm_clear_damage(gdrm);
+	mutex_unlock(&gdrm->damage_lock);
+
+	if (!fb)
+		goto out;
+
+	format = fb->format;
+	if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format)
+		format = gdrm->xrgb8888_emulation_format;
+
+	/* Split update if it's too big */
+	pitch = drm_format_info_min_pitch(format, 0, drm_rect_width(&damage));
+	lines = drm_rect_height(&damage);
+
+	if (gdrm->bulk_len < lines * pitch)
+		lines = gdrm->bulk_len / pitch;
+
+	for (i = 0; i < DIV_ROUND_UP(drm_rect_height(&damage), lines); i++) {
+		struct drm_rect rect = damage;
+
+		rect.y1 += i * lines;
+		rect.y2 = min_t(u32, rect.y1 + lines, damage.y2);
+
+		ret = gud_drm_fb_flush(gdrm, fb, format, &rect);
+		if (ret &&
+		    (ret != -ENODEV && ret != -ECONNRESET && ret != -ESHUTDOWN && ret != EPROTO))
+			dev_err_once(fb->dev->dev, "Failed to flush framebuffer: error=%d\n", ret);
+	}
+
+	drm_framebuffer_put(fb);
+out:
+	drm_dev_exit(idx);
+}
+
+static void gud_drm_fb_queue_damage(struct gud_drm_device *gdrm,
+				    struct drm_framebuffer *fb,
+				    struct drm_rect *damage)
+{
+	struct drm_framebuffer *old_fb = NULL;
+
+	mutex_lock(&gdrm->damage_lock);
+
+	if (fb != gdrm->fb) {
+		old_fb = gdrm->fb;
+		drm_framebuffer_get(fb);
+		gdrm->fb = fb;
+	}
+
+	gdrm->damage.x1 = min(gdrm->damage.x1, damage->x1);
+	gdrm->damage.y1 = min(gdrm->damage.y1, damage->y1);
+	gdrm->damage.x2 = max(gdrm->damage.x2, damage->x2);
+	gdrm->damage.y2 = max(gdrm->damage.y2, damage->y2);
+
+	mutex_unlock(&gdrm->damage_lock);
+
+	queue_work(system_long_wq, &gdrm->work);
+
+	if (old_fb)
+		drm_framebuffer_put(old_fb);
+}
+
+int gud_drm_pipe_check(struct drm_simple_display_pipe *pipe,
+		       struct drm_plane_state *new_plane_state,
+		       struct drm_crtc_state *new_crtc_state)
+{
+	struct gud_drm_device *gdrm = to_gud_drm_device(pipe->crtc.dev);
+	struct drm_plane_state *old_plane_state = pipe->plane.state;
+	const struct drm_display_mode *mode = &new_crtc_state->mode;
+	struct drm_atomic_state *state = new_plane_state->state;
+	struct drm_framebuffer *old_fb = old_plane_state->fb;
+	struct drm_connector_state *connector_state = NULL;
+	struct drm_framebuffer *fb = new_plane_state->fb;
+	const struct drm_format_info *format = fb->format;
+	struct gud_drm_req_set_state *req;
+	struct drm_connector *connector;
+	int idx, ret, num_properties;
+	unsigned int i;
+	size_t len;
+
+	if (WARN_ON_ONCE(!fb))
+		return -EINVAL;
+
+	if (old_plane_state->rotation != new_plane_state->rotation)
+		new_crtc_state->mode_changed = true;
+
+	if (old_fb && old_fb->format != format)
+		new_crtc_state->mode_changed = true;
+
+	if (!new_crtc_state->mode_changed && !new_crtc_state->connectors_changed)
+		return 0;
+
+	/* Only one connector is supported */
+	if (hweight32(new_crtc_state->connector_mask) != 1)
+		return -EINVAL;
+
+	if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format)
+		format = gdrm->xrgb8888_emulation_format;
+
+	for_each_new_connector_in_state(state, connector, connector_state, i)
+		break;
+
+	if (!connector_state) {
+		struct drm_connector_list_iter conn_iter;
+
+		/* We always send the full state to the device, so get the connector state */
+
+		drm_connector_list_iter_begin(pipe->crtc.dev, &conn_iter);
+		drm_for_each_connector_iter(connector, &conn_iter) {
+			if (new_crtc_state->connector_mask & drm_connector_mask(connector))
+				break;
+		}
+		drm_connector_list_iter_end(&conn_iter);
+
+		if (WARN_ON_ONCE(!connector))
+			return -ENOENT;
+
+		connector_state = drm_atomic_get_connector_state(state, connector);
+		if (IS_ERR(connector_state))
+			return PTR_ERR(connector_state);
+	}
+
+	num_properties = gud_drm_connector_fill_properties(connector, NULL, NULL);
+	if (num_properties < 0)
+		return num_properties;
+
+	num_properties += gdrm->num_properties;
+
+	len = struct_size(req, properties, num_properties);
+	req = kzalloc(len, GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	gud_drm_from_display_mode(&req->mode, mode);
+
+	req->format = cpu_to_le32(format->format);
+	req->connector = drm_connector_index(connector);
+	req->num_properties = num_properties;
+
+	num_properties = gud_drm_connector_fill_properties(connector, connector_state,
+							   req->properties);
+
+	for (i = 0; i < gdrm->num_properties; i++) {
+		u16 prop = gdrm->properties[i];
+		u64 val;
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_ROTATION:
+			val = new_plane_state->rotation;
+			break;
+		default:
+			WARN_ON_ONCE(1);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		req->properties[num_properties + i].prop = cpu_to_le16(prop);
+		req->properties[num_properties + i].val = cpu_to_le64(val);
+	}
+
+	if (!drm_dev_enter(fb->dev, &idx)) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	ret = gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_STATE_CHECK, 0, req, len);
+
+	drm_dev_exit(idx);
+out:
+	kfree(req);
+
+	return ret;
+}
+
+void gud_drm_pipe_update(struct drm_simple_display_pipe *pipe,
+			 struct drm_plane_state *old_state)
+{
+	struct drm_device *drm = pipe->crtc.dev;
+	struct gud_drm_device *gdrm = to_gud_drm_device(drm);
+	struct drm_plane_state *state = pipe->plane.state;
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_crtc *crtc = &pipe->crtc;
+	struct drm_rect damage;
+	int idx;
+
+	if (!drm_dev_enter(drm, &idx))
+		return;
+
+	if (!old_state->fb)
+		gud_drm_usb_write8(gdrm, GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE, 1);
+
+	if (fb && (crtc->state->mode_changed || crtc->state->connectors_changed))
+		gud_drm_usb_set(gdrm, GUD_DRM_USB_REQ_SET_STATE_COMMIT, 0, NULL, 0);
+
+	if (crtc->state->active_changed)
+		gud_drm_usb_write8(gdrm, GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE, crtc->state->active);
+
+	if (drm_atomic_helper_damage_merged(old_state, state, &damage))
+		gud_drm_fb_queue_damage(gdrm, fb, &damage);
+
+	if (!fb) {
+		cancel_work_sync(&gdrm->work);
+
+		mutex_lock(&gdrm->damage_lock);
+		if (gdrm->fb) {
+			drm_framebuffer_put(gdrm->fb);
+			gdrm->fb = NULL;
+		}
+		gud_drm_clear_damage(gdrm);
+		mutex_unlock(&gdrm->damage_lock);
+
+		gud_drm_usb_write8(gdrm, GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE, 0);
+	}
+
+	drm_dev_exit(idx);
+}
diff --git a/include/drm/gud_drm.h b/include/drm/gud_drm.h
new file mode 100644
index 000000000000..15bb30577b57
--- /dev/null
+++ b/include/drm/gud_drm.h
@@ -0,0 +1,356 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#ifndef __LINUX_GUD_DRM_H
+#define __LINUX_GUD_DRM_H
+
+#include <drm/drm_modes.h>
+#include <linux/types.h>
+#include <uapi/drm/drm_fourcc.h>
+#include <uapi/linux/usb/ch9.h>
+
+/*
+ * Maximum size of a control message, fits 120 display modes.
+ * If this needs to increase, the IN side in f_gud_drm_setup()
+ * needs fixing.
+ */
+#define GUD_DRM_MAX_TRANSFER_SIZE	SZ_4K
+
+#define GUD_DRM_USB_DT_DISPLAY		(USB_TYPE_VENDOR | 0x4)
+
+/*
+ * struct gud_drm_display_descriptor - Display descriptor
+ * @bLength: Size of descriptor in bytes
+ * @bDescriptorType: DescriptorType (GUD_DRM_USB_DT_DISPLAY)
+ * @bVersion: Protocol version
+ * @bMaxBufferSizeOrder: Maximum buffer size the device can handle as log2
+ * @bmFlags: Currently unused, should be set to zero
+ * @bCompression: Supported compression types
+ * @dwMinWidth: Minimum pixel width the controller can handle
+ * @dwMaxWidth: Maximum width
+ * @dwMinHeight: Minimum height
+ * @dwMaxHeight: Maximum height
+ * @bNumFormats: Number of supported pixel formats
+ * @bNumProperties: Number of properties that are not connector porperties
+ * @bNumConnectors: Number of connectors
+ *
+ * Devices that have only one display mode will have dwMinWidth == dwMaxWidth
+ * and dwMinHeight == dwMaxHeight.
+ *
+ */
+struct gud_drm_display_descriptor {
+	__u8 bLength;
+	__u8 bDescriptorType;
+
+	__u8 bVersion;
+	__u8 bMaxBufferSizeOrder;
+	__le32 bmFlags;
+
+	__u8 bCompression;
+#define GUD_DRM_COMPRESSION_LZ4		BIT(0)
+
+	__le32 dwMinWidth;
+	__le32 dwMaxWidth;
+	__le32 dwMinHeight;
+	__le32 dwMaxHeight;
+
+	__u8 bNumFormats;
+	__u8 bNumProperties;
+	__u8 bNumConnectors;
+} __packed;
+
+/*
+ * struct gud_drm_req_get_status - Status request
+ * @flags: Flags
+ * @errno: Linux errno value
+ *
+ * The host keeps polling for status as long as the GUD_DRM_STATUS_PENDING flag
+ * is set (or until timeout). Requested using: USB_REQ_GET_STATUS.
+ */
+struct gud_drm_req_get_status {
+	__u8 flags;
+#define GUD_DRM_STATUS_PENDING	BIT(0)
+	__u8 errno;
+} __packed;
+
+/*
+ * struct gud_drm_property - Property
+ * @prop: Property
+ * @val: Value
+ */
+struct gud_drm_property {
+	__le16 prop;
+	__le64 val;
+} __packed;
+
+/* See &drm_display_mode for the meaning of these fields */
+struct gud_drm_display_mode {
+	__le32 clock;
+	__le16 hdisplay;
+	__le16 hsync_start;
+	__le16 hsync_end;
+	__le16 htotal;
+	__le16 hskew;
+	__le16 vdisplay;
+	__le16 vsync_start;
+	__le16 vsync_end;
+	__le16 vtotal;
+	__le16 vscan;
+	__le32 vrefresh;
+	__le32 flags;
+	__u8 type;
+} __packed;
+
+/*
+ * struct gud_drm_req_get_connector - Connector descriptor
+ * @connector_type: Connector type (DRM_MODE_CONNECTOR_*)
+ * @flags: Flags
+ * @num_properties: Number of supported properties
+ */
+struct gud_drm_req_get_connector {
+	__u8 connector_type;
+
+	__le32 flags;
+#define GUD_DRM_CONNECTOR_FLAGS_POLL	BIT(0)
+
+	__u8 num_properties;
+} __packed;
+
+/*
+ * struct gud_drm_req_get_connector_status - Connector status
+ * @status: Status, see &drm_connector_status
+ * @num_modes: Number of available display modes
+ * @modes_array_checksum: CRC-CCITT checksum of the display mode array in little endian format
+ * @edid_len: Length of EDID data
+ * @edid_checksum: CRC-CCITT checksum of EDID data
+ *
+ * If both @num_modes and @edid_len are zero, connector status is set to
+ * disconnected. If @num_modes is zero, edid is used to create display modes.
+ * If both are set, edid is just passed on to userspace in the EDID connector
+ * property.
+ *
+ * Display modes and EDID are only requested if number/length or crc differs.
+ */
+struct gud_drm_req_get_connector_status {
+	__u8 status;
+#define GUD_DRM_CONNECTOR_STATUS_MASK		0xf /* Only 2 bits are currently used for status */
+#define GUD_DRM_CONNECTOR_STATUS_CHANGED	BIT(7)
+	__u16 num_modes;
+	__u16 edid_len;
+} __packed;
+
+/*
+ * struct gud_drm_req_set_buffer - Set buffer transfer info
+ * @x: X position of rectangle
+ * @y: Y position
+ * @width: Pixel width of rectangle
+ * @height: Pixel height
+ * @length: Buffer length in bytes
+ * @compression: Transfer compression
+ * @compressed_length: Compressed buffer length
+ *
+ * @x, @y, @width and @height specifies the rectangle where the buffer should be
+ * placed inside the framebuffer.
+ */
+struct gud_drm_req_set_buffer {
+	__le32 x;
+	__le32 y;
+	__le32 width;
+	__le32 height;
+
+	__le32 length;
+	__u8 compression;
+	__le32 compressed_length;
+} __packed;
+
+/*
+ * struct gud_drm_req_set_state - Set display state
+ * @mode: Display mode
+ * @format: Pixel format
+ * @connector: Connector index
+ * @num_properties: Number of properties in the state
+ * @properties: Array of properties
+ *
+ * The entire state is transferred each time there's a change.
+ */
+struct gud_drm_req_set_state {
+	struct gud_drm_display_mode mode;
+	__le32 format;
+	__u8 connector;
+	__u8 num_properties;
+	struct gud_drm_property properties[];
+} __packed;
+
+/*
+ * Internal monochrome transfer format presented to userspace as XRGB8888.
+ * Pixel lines are byte aligned.
+ */
+#define GUD_DRM_FORMAT_R1	fourcc_code('R', '1', ' ', ' ')
+
+/* List of supported connector properties: */
+
+/* TV related properties, see &drm_connector and &drm_tv_connector_state */
+#define GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR		1
+#define GUD_DRM_PROPERTY_TV_LEFT_MARGIN			2
+#define GUD_DRM_PROPERTY_TV_RIGHT_MARGIN		3
+#define GUD_DRM_PROPERTY_TV_TOP_MARGIN			4
+#define GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN		5
+/* Number of modes are placed at _SHIFT in val on retrieval */
+#define GUD_DRM_PROPERTY_TV_MODE			6
+  #define GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT   16
+#define GUD_DRM_PROPERTY_TV_BRIGHTNESS			7
+#define GUD_DRM_PROPERTY_TV_CONTRAST			8
+#define GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION		9
+#define GUD_DRM_PROPERTY_TV_OVERSCAN			10
+#define GUD_DRM_PROPERTY_TV_SATURATION			11
+#define GUD_DRM_PROPERTY_TV_HUE				12
+
+/*
+ * Backlight brightness is in the range 0-100 inclusive. The value represents
+ * the human perceptual brightness and not a linear PWM value. 0 is minimum
+ * brightness which should not turn the backlight completely off. The DPMS
+ * connector property should be used to control power which will trigger a
+ * GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE request.
+ *
+ * This is not a real DRM property, but rather a fake one used for the backlight
+ * device. See drm_backlight_register() for more details.
+ */
+#define GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS		13
+
+/* List of supported properties that are not connector propeties: */
+
+/*
+ * Plane rotation. Should return the supported bitmask on
+ * GUD_DRM_USB_REQ_GET_PROPERTIES, see drm_plane_create_rotation_property().
+ */
+#define GUD_DRM_PROPERTY_ROTATION			50
+
+/* USB Control requests: */
+
+/* Get supported pixel formats as an array of fourcc codes. See include/uapi/drm/drm_fourcc.h */
+#define GUD_DRM_USB_REQ_GET_FORMATS			0x40
+
+/* Get supported properties that are not connector propeties as a &gud_drm_property array */
+#define GUD_DRM_USB_REQ_GET_PROPERTIES			0x41
+
+/* Get connector descriptor */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR			0x50
+
+/* Get properties supported by the connector as a &gud_drm_property array */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_PROPERTIES	0x51
+
+/*
+ * Issued when there's a tv.mode property present.
+ * Gets an array of tv.mode enum names each entry of length DRM_PROP_NAME_LEN.
+ */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_TV_MODE_VALUES	0x52
+
+/* When userspace checks status, this is issued first, not used for poll requests. */
+#define GUD_DRM_USB_REQ_SET_CONNECTOR_FORCE_DETECT	0x53
+
+/* Get connector status as &gud_drm_req_get_connector_status. */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_STATUS		0x54
+
+/* Get &gud_drm_display_mode array of supported display modes */
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_MODES		0x55
+
+#define GUD_DRM_USB_REQ_GET_CONNECTOR_EDID		0x56
+
+/* Set buffer properties before bulk transfer as &gud_drm_req_set_buffer */
+#define GUD_DRM_USB_REQ_SET_BUFFER			0x60
+
+/* Check display configuration as &gud_drm_req_set_state */
+#define GUD_DRM_USB_REQ_SET_STATE_CHECK			0x61
+
+/* Apply the prevoius _STATE_CHECK configuration */
+#define GUD_DRM_USB_REQ_SET_STATE_COMMIT		0x62
+
+ /* Enable/disable the display controller, value is u8 0/1 */
+#define GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE		0x63
+
+/* Enable/disable display/output (DPMS), value is u8 0/1 */
+#define GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE		0x64
+
+static inline void gud_drm_from_display_mode(struct gud_drm_display_mode *dst,
+					     const struct drm_display_mode *src)
+{
+	u32 flags = src->flags;
+
+	switch (src->picture_aspect_ratio) {
+	case HDMI_PICTURE_ASPECT_4_3:
+		flags |= DRM_MODE_FLAG_PIC_AR_4_3;
+		break;
+	case HDMI_PICTURE_ASPECT_16_9:
+		flags |= DRM_MODE_FLAG_PIC_AR_16_9;
+		break;
+	case HDMI_PICTURE_ASPECT_64_27:
+		flags |= DRM_MODE_FLAG_PIC_AR_64_27;
+		break;
+	case HDMI_PICTURE_ASPECT_256_135:
+		flags |= DRM_MODE_FLAG_PIC_AR_256_135;
+		break;
+	default:
+		flags |= DRM_MODE_FLAG_PIC_AR_NONE;
+		break;
+	}
+
+	dst->clock = cpu_to_le32(src->clock);
+	dst->hdisplay = cpu_to_le16(src->hdisplay);
+	dst->hsync_start = cpu_to_le16(src->hsync_start);
+	dst->hsync_end = cpu_to_le16(src->hsync_end);
+	dst->htotal = cpu_to_le16(src->htotal);
+	dst->hskew = cpu_to_le16(src->hskew);
+	dst->vdisplay = cpu_to_le16(src->vdisplay);
+	dst->vsync_start = cpu_to_le16(src->vsync_start);
+	dst->vsync_end = cpu_to_le16(src->vsync_end);
+	dst->vtotal = cpu_to_le16(src->vtotal);
+	dst->vscan = cpu_to_le16(src->vscan);
+	dst->vrefresh = cpu_to_le32(src->vrefresh);
+	dst->flags = cpu_to_le32(flags);
+	dst->type = src->type;
+}
+
+static inline void gud_drm_to_display_mode(struct drm_display_mode *dst,
+					   const struct gud_drm_display_mode *src)
+{
+	u32 flags = le32_to_cpu(src->flags);
+
+	dst->clock = le32_to_cpu(src->clock);
+	dst->hdisplay = le16_to_cpu(src->hdisplay);
+	dst->hsync_start = le16_to_cpu(src->hsync_start);
+	dst->hsync_end = le16_to_cpu(src->hsync_end);
+	dst->htotal = le16_to_cpu(src->htotal);
+	dst->hskew = le16_to_cpu(src->hskew);
+	dst->vdisplay = le16_to_cpu(src->vdisplay);
+	dst->vsync_start = le16_to_cpu(src->vsync_start);
+	dst->vsync_end = le16_to_cpu(src->vsync_end);
+	dst->vtotal = le16_to_cpu(src->vtotal);
+	dst->vscan = le16_to_cpu(src->vscan);
+	dst->vrefresh = le32_to_cpu(src->vrefresh);
+	dst->flags = flags & ~DRM_MODE_FLAG_PIC_AR_MASK;
+	dst->type = src->type;
+
+	switch (flags & DRM_MODE_FLAG_PIC_AR_MASK) {
+	case DRM_MODE_FLAG_PIC_AR_4_3:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3;
+		break;
+	case DRM_MODE_FLAG_PIC_AR_16_9:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;
+		break;
+	case DRM_MODE_FLAG_PIC_AR_64_27:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27;
+		break;
+	case DRM_MODE_FLAG_PIC_AR_256_135:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135;
+		break;
+	default:
+		dst->picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE;
+		break;
+	}
+
+	drm_mode_set_name(dst);
+}
+
+#endif
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 09/10] drm/gud: Add functionality for the USB gadget side
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes

Since the USB gadget/device has to reach into the DRM internals, part of
the code is placed in the DRM subsystem as a separate module. All calls
into this module runs in process context and are serialized, except one
function that runs in interrupt context.

Since both the gadget side and the DRM side can disable the gadget,
special care has been taken to make that safe.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/gud/Kconfig          |    6 +
 drivers/gpu/drm/gud/Makefile         |    1 +
 drivers/gpu/drm/gud/gud_drm_gadget.c | 1169 ++++++++++++++++++++++++++
 include/drm/gud_drm.h                |   13 +
 4 files changed, 1189 insertions(+)
 create mode 100644 drivers/gpu/drm/gud/gud_drm_gadget.c

diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig
index 203d4490f1c7..99bfbfbf9023 100644
--- a/drivers/gpu/drm/gud/Kconfig
+++ b/drivers/gpu/drm/gud/Kconfig
@@ -12,3 +12,9 @@ config DRM_GUD
 	  adapters.
 
 	  If M is selected the module will be called gud_drm.
+
+config DRM_GUD_GADGET
+	tristate
+	depends on DRM
+	select LZ4_DECOMPRESS
+	select BACKLIGHT_CLASS_DEVICE
diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile
index 73ed7ef3da94..fb5cfbd93d0f 100644
--- a/drivers/gpu/drm/gud/Makefile
+++ b/drivers/gpu/drm/gud/Makefile
@@ -2,3 +2,4 @@
 
 gud_drm-objs			:= gud_drm_drv.o gud_drm_pipe.o gud_drm_connector.o
 obj-$(CONFIG_DRM_GUD)		+= gud_drm.o
+obj-$(CONFIG_DRM_GUD_GADGET)	+= gud_drm_gadget.o
diff --git a/drivers/gpu/drm/gud/gud_drm_gadget.c b/drivers/gpu/drm/gud/gud_drm_gadget.c
new file mode 100644
index 000000000000..a9c916abec85
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_gadget.c
@@ -0,0 +1,1169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/lz4.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_mode_object.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_rect.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+/*
+ * Concurrency:
+ * Calls into this module from f_gud_drm are serialized and run in process
+ * context except gud_drm_gadget_ctrl_get() which is run in interrupt context.
+ *
+ * Termination:
+ * A DRM client can not release itself, only the DRM driver which resources the
+ * client uses can do that.
+ * This means that there are 2 paths to stop the gadget function:
+ * - Unregistering the DRM driver (module unload)
+ * - Disabling the USB gadget (configfs unbind)
+ *
+ * A use counter protects the gadget should the client go away. A kref is used
+ * to control the release of the gud_drm_gadget structure shared by the 2 actors.
+ *
+ * Backlight:
+ * If there's a backlight device it's attached to the first connector.
+ */
+
+struct gud_drm_gadget_connector {
+	struct drm_connector *connector;
+
+	const struct gud_drm_property *properties;
+	unsigned int num_properties;
+	const char *tv_mode_enum_names;
+	unsigned int num_tv_mode_enum_names;
+	struct backlight_device *backlight;
+
+	spinlock_t lock; /* Protects the following members: */
+	enum drm_connector_status status;
+	unsigned int width_mm;
+	unsigned int height_mm;
+	struct gud_drm_display_mode *modes;
+	unsigned int num_modes;
+	void *edid;
+	size_t edid_len;
+	bool changed;
+};
+
+struct gud_drm_gadget {
+	struct kref refcount;
+	refcount_t usecnt;
+	struct drm_client_dev client;
+	struct backlight_device *backlight;
+
+	const u32 *formats;
+	unsigned int format_count;
+
+	const struct gud_drm_property *properties;
+	unsigned int num_properties;
+
+	struct gud_drm_gadget_connector *connectors;
+	unsigned int connector_count;
+
+	struct drm_rect set_buffer_rect;
+	u32 set_buffer_length;
+	u8 set_buffer_compression;
+	u32 set_buffer_compressed_length;
+
+	struct drm_client_buffer *buffer;
+	struct drm_client_buffer *buffer_check;
+	u8 brightness;
+	bool check_ok;
+
+	size_t max_buffer_size;
+	void *work_buf;
+};
+
+static int gud_drm_gadget_probe_connector(struct gud_drm_gadget_connector *gconn)
+{
+	struct drm_connector *connector = gconn->connector;
+	struct gud_drm_display_mode *modes = NULL;
+	struct drm_device *drm = connector->dev;
+	struct drm_display_mode *mode;
+	void *edid_data, *edid = NULL;
+	unsigned int num_modes = 0;
+	size_t edid_len = 0;
+	unsigned long flags;
+	unsigned int i = 0;
+	int ret = 0;
+
+	mutex_lock(&drm->mode_config.mutex);
+
+	connector->funcs->fill_modes(connector,
+				     drm->mode_config.max_width,
+				     drm->mode_config.max_height);
+
+	list_for_each_entry(mode, &connector->modes, head)
+		num_modes++;
+
+	if (!num_modes)
+		goto update;
+
+	modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL);
+	if (!modes) {
+		ret = -ENOMEM;
+		num_modes = 0;
+		goto update;
+	}
+
+	list_for_each_entry(mode, &connector->modes, head)
+		gud_drm_from_display_mode(&modes[i++], mode);
+
+	if (!connector->edid_blob_ptr)
+		goto update;
+
+	edid_data = connector->edid_blob_ptr->data;
+	edid_len = connector->edid_blob_ptr->length;
+	if (!edid_data || !edid_len) {
+		edid_len = 0;
+		goto update;
+	}
+
+	edid = kmemdup(edid_data, edid_len, GFP_KERNEL);
+	if (!edid) {
+		ret = -ENOMEM;
+		edid_len = 0;
+	}
+
+update:
+	spin_lock_irqsave(&gconn->lock, flags);
+	if (gconn->status != connector->status || gconn->num_modes != num_modes ||
+	    gconn->edid_len != edid_len ||
+	    (gconn->modes && modes && memcmp(gconn->modes, modes, num_modes * sizeof(*modes))) ||
+	    (gconn->edid && edid && memcmp(gconn->edid, edid, edid_len)))
+		gconn->changed = true;
+	swap(gconn->modes, modes);
+	gconn->num_modes = num_modes;
+	swap(gconn->edid, edid);
+	gconn->edid_len = edid_len;
+	gconn->width_mm = connector->display_info.width_mm;
+	gconn->height_mm = connector->display_info.height_mm;
+	gconn->status = connector->status;
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	mutex_unlock(&drm->mode_config.mutex);
+
+	kfree(edid);
+	kfree(modes);
+
+	return ret;
+}
+
+static void gud_drm_gadget_probe_connectors(struct gud_drm_gadget *gdg)
+{
+	unsigned int i;
+
+	for (i = 0; i < gdg->connector_count; i++)
+		gud_drm_gadget_probe_connector(&gdg->connectors[i]);
+}
+
+static bool gud_drm_gadget_check_buffer(struct gud_drm_gadget *gdg,
+					struct drm_client_buffer *buffer,
+					struct drm_display_mode *mode,
+					u32 format)
+{
+	struct drm_framebuffer *fb;
+
+	if (!buffer)
+		return false;
+
+	fb = buffer->fb;
+
+	return fb->format->format == format &&
+	       fb->width == mode->hdisplay && fb->height == mode->vdisplay;
+}
+
+static bool gud_drm_gadget_set_connector_property(struct drm_client_dev *client,
+						  struct drm_connector *connector,
+						  u16 prop, u64 val, int *ret)
+{
+	struct drm_mode_config *config = &connector->dev->mode_config;
+	struct drm_property *property;
+
+	switch (prop) {
+	case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+		property = config->tv_select_subconnector_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+		property = config->tv_left_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+		property = config->tv_right_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+		property = config->tv_top_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+		property = config->tv_bottom_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_MODE:
+		property = config->tv_mode_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+		property = config->tv_brightness_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_CONTRAST:
+		property = config->tv_contrast_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+		property = config->tv_flicker_reduction_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_OVERSCAN:
+		property = config->tv_overscan_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_SATURATION:
+		property = config->tv_saturation_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_HUE:
+		property = config->tv_hue_property;
+		break;
+	default:
+		return false;
+	}
+
+	*ret = drm_client_modeset_set_property(client, &connector->base, property, val);
+
+	return true;
+}
+
+static int gud_drm_gadget_check(struct gud_drm_gadget *gdg, struct gud_drm_req_set_state *req,
+				size_t size)
+{
+	struct drm_client_dev *client = &gdg->client;
+	u32 format = le32_to_cpu(req->format);
+	struct drm_client_buffer *buffer;
+	struct drm_connector *connector;
+	struct drm_display_mode mode;
+	unsigned int i;
+	void *vaddr;
+	int ret;
+
+	if (size < sizeof(struct gud_drm_req_set_state))
+		return -EINVAL;
+
+	if (size != struct_size(req, properties, req->num_properties))
+		return -EINVAL;
+
+	memset(&mode, 0, sizeof(mode));
+	gud_drm_to_display_mode(&mode, &req->mode);
+
+	gdg->check_ok = false;
+
+	if (!mode.hdisplay || !format)
+		return -EINVAL;
+
+	if (req->connector >= gdg->connector_count)
+		return -EINVAL;
+
+	connector = gdg->connectors[req->connector].connector;
+
+	if (gdg->buffer_check) {
+		drm_client_framebuffer_delete(gdg->buffer_check);
+		gdg->buffer_check = NULL;
+	}
+
+	if (!gud_drm_gadget_check_buffer(gdg, gdg->buffer, &mode, format)) {
+		buffer = drm_client_framebuffer_create(client, mode.hdisplay, mode.vdisplay,
+						       format);
+		if (IS_ERR(buffer))
+			return PTR_ERR(buffer);
+
+		vaddr = drm_client_buffer_vmap(buffer);
+		if (IS_ERR(vaddr)) {
+			drm_client_framebuffer_delete(buffer);
+			return PTR_ERR(vaddr);
+		}
+
+		gdg->buffer_check = buffer;
+	} else {
+		buffer = gdg->buffer;
+	}
+
+	ret = drm_client_modeset_set(client, connector, &mode, buffer->fb);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < req->num_properties; i++) {
+		u16 prop = le16_to_cpu(req->properties[i].prop);
+		u64 val = le64_to_cpu(req->properties[i].val);
+
+		if (gud_drm_gadget_set_connector_property(client, connector, prop, val, &ret)) {
+			if (ret)
+				return ret;
+			continue;
+		}
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS:
+			if (val > 100)
+				return -EINVAL;
+			gdg->brightness = val;
+			break;
+		case GUD_DRM_PROPERTY_ROTATION:
+			ret = drm_client_modeset_set_rotation(client, val);
+			break;
+		default:
+			pr_err("%s: Unknown property: %u\n", __func__, prop);
+			continue;
+		}
+
+		if (ret)
+			return ret;
+	}
+
+	ret = drm_client_modeset_check(&gdg->client);
+	if (ret)
+		return ret;
+
+	gdg->check_ok = true;
+
+	return 0;
+}
+
+static int gud_drm_gadget_commit(struct gud_drm_gadget *gdg)
+{
+	int ret;
+
+	if (!gdg->check_ok)
+		return -EINVAL;
+
+	if (gdg->backlight) {
+		int val, max_brightness = gdg->backlight->props.max_brightness;
+
+		val = DIV64_U64_ROUND_UP(gdg->brightness * max_brightness, 100);
+		ret = backlight_device_set_brightness(gdg->backlight, val);
+		if (ret)
+			return ret;
+	}
+
+	ret = drm_client_modeset_commit(&gdg->client);
+	if (ret)
+		return ret;
+
+	if (gdg->buffer_check) {
+		drm_client_framebuffer_delete(gdg->buffer);
+		gdg->buffer = gdg->buffer_check;
+		gdg->buffer_check = NULL;
+	}
+
+	return 0;
+}
+
+static size_t gud_drm_gadget_write_buffer_memcpy(struct drm_client_buffer *buffer,
+						 const void *src, size_t len,
+						 struct drm_rect *rect)
+{
+	unsigned int cpp = buffer->fb->format->cpp[0];
+	size_t dst_pitch = buffer->fb->pitches[0];
+	size_t src_pitch = drm_rect_width(rect) * cpp;
+	unsigned int y;
+	void *dst;
+
+	/* Get the address, it's already mapped */
+	dst = drm_client_buffer_vmap(buffer);
+	dst += rect->y1 * dst_pitch;
+	dst += rect->x1 * cpp;
+
+	for (y = 0; y < drm_rect_height(rect) && len; y++) {
+		src_pitch = min(src_pitch, len);
+		memcpy(dst, src, src_pitch);
+		src += src_pitch;
+		dst += dst_pitch;
+		len -= src_pitch;
+	}
+
+	return len;
+}
+
+static bool gud_drm_gadget_check_rect(struct drm_client_buffer *buffer, struct drm_rect *rect)
+{
+	return buffer->fb && rect->x1 < rect->x2 && rect->y1 < rect->y2 &&
+	       rect->x2 <= buffer->fb->width && rect->y2 <= buffer->fb->height;
+}
+
+int gud_drm_gadget_write_buffer(struct gud_drm_gadget *gdg, const void *buf, size_t len)
+{
+	struct drm_client_buffer *buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check;
+	struct drm_rect *rect = &gdg->set_buffer_rect;
+	u8 compression = gdg->set_buffer_compression;
+	struct drm_framebuffer *fb;
+	size_t remain;
+	int ret;
+
+	pr_debug("%s: len=%zu compression=0x%x\n", __func__, len, compression);
+
+	if (WARN_ON_ONCE(!buffer))
+		return -ENOMEM;
+
+	if (!gud_drm_gadget_check_rect(buffer, rect)) {
+		pr_err("%s: Rectangle doesn't fit\n", __func__);
+		return -EINVAL;
+	}
+
+	fb = buffer->fb;
+
+	if (compression & GUD_DRM_COMPRESSION_LZ4) {
+		if (len != gdg->set_buffer_compressed_length) {
+			pr_err("%s: Buffer compressed len differs: %zu != %zu\n",
+			       __func__, len, gdg->set_buffer_compressed_length);
+			return -EINVAL;
+		}
+
+		ret = LZ4_decompress_safe(buf, gdg->work_buf, len, gdg->max_buffer_size);
+		if (ret < 0) {
+			pr_err("%s: Failed to decompress buffer\n", __func__);
+			return -EIO;
+		}
+
+		buf = gdg->work_buf;
+		len = ret;
+	}
+
+	if (len != gdg->set_buffer_length) {
+		pr_err("%s: Buffer len differs: %zu != %zu\n",
+		       __func__, len, gdg->set_buffer_length);
+		return -EINVAL;
+	}
+
+	if (len > (drm_rect_width(rect) * drm_rect_height(rect) * fb->format->cpp[0])) {
+		pr_err("%s: Buffer is too big for rectangle\n", __func__);
+		return -EINVAL;
+	}
+
+	remain = gud_drm_gadget_write_buffer_memcpy(buffer, buf, len, rect);
+	if (remain) {
+		pr_err("%s: Failed to write buffer: remain=%zu\n", __func__, remain);
+		return -EIO;
+	}
+
+	return drm_client_framebuffer_flush(buffer, rect);
+}
+EXPORT_SYMBOL(gud_drm_gadget_write_buffer);
+
+int gud_drm_gadget_set_buffer(struct gud_drm_gadget *gdg, struct gud_drm_req_set_buffer *req)
+{
+	u32 compressed_length = le32_to_cpu(req->compressed_length);
+	u32 length = le32_to_cpu(req->length);
+	struct drm_client_buffer *buffer;
+	struct drm_rect rect;
+	int ret = 0;
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check;
+	if (!buffer) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	drm_rect_init(&rect, le32_to_cpu(req->x), le32_to_cpu(req->y),
+		      le32_to_cpu(req->width), le32_to_cpu(req->height));
+
+	pr_debug("%s: " DRM_RECT_FMT "\n", __func__, DRM_RECT_ARG(&rect));
+
+	if (!gud_drm_gadget_check_rect(buffer, &rect)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (req->compression & ~GUD_DRM_COMPRESSION_LZ4) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	gdg->set_buffer_rect = rect;
+	gdg->set_buffer_length = length;
+
+	if (req->compression) {
+		if (!compressed_length) {
+			ret = -EINVAL;
+			goto out;
+		}
+		gdg->set_buffer_compression = req->compression;
+		gdg->set_buffer_compressed_length = compressed_length;
+		length = compressed_length;
+	} else {
+		gdg->set_buffer_compression = 0;
+		gdg->set_buffer_compressed_length = 0;
+	}
+out:
+	refcount_dec(&gdg->usecnt);
+
+	return ret ? ret : length;
+}
+EXPORT_SYMBOL(gud_drm_gadget_set_buffer);
+
+static void gud_drm_gadget_delete_buffers(struct gud_drm_gadget *gdg)
+{
+	drm_client_framebuffer_delete(gdg->buffer_check);
+	drm_client_framebuffer_delete(gdg->buffer);
+	gdg->buffer_check = NULL;
+	gdg->buffer = NULL;
+}
+
+int gud_drm_gadget_disable_pipe(struct gud_drm_gadget *gdg)
+{
+	int ret;
+
+	drm_client_modeset_set(&gdg->client, NULL, NULL, NULL);
+	ret = drm_client_modeset_commit(&gdg->client);
+	if (ret)
+		return ret;
+
+	gud_drm_gadget_delete_buffers(gdg);
+
+	return 0;
+}
+EXPORT_SYMBOL(gud_drm_gadget_disable_pipe);
+
+static int gud_drm_gadget_ctrl_get_display_descriptor(struct gud_drm_gadget *gdg, u16 value,
+						      void *data, size_t size)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct gud_drm_display_descriptor desc;
+	u8 type = value >> 8;
+	u8 index = value & 0xff;
+
+	if (type != GUD_DRM_USB_DT_DISPLAY || index)
+		return -EINVAL;
+
+	desc.bLength = sizeof(desc);
+	desc.bDescriptorType = GUD_DRM_USB_DT_DISPLAY;
+
+	desc.bVersion = 1;
+	desc.bMaxBufferSizeOrder = ilog2(gdg->max_buffer_size);
+	desc.bmFlags = 0;
+	desc.bCompression = GUD_DRM_COMPRESSION_LZ4;
+
+	desc.dwMinWidth = cpu_to_le32(drm->mode_config.min_width);
+	desc.dwMaxWidth = cpu_to_le32(drm->mode_config.max_width);
+	desc.dwMinHeight = cpu_to_le32(drm->mode_config.min_height);
+	desc.dwMaxHeight = cpu_to_le32(drm->mode_config.max_height);
+	desc.bNumFormats = gdg->format_count;
+	desc.bNumProperties = gdg->num_properties;
+	desc.bNumConnectors = gdg->connector_count;
+
+	size = min(size, sizeof(desc));
+	memcpy(data, &desc, size);
+
+	return size;
+}
+
+static void gud_drm_gadget_ctrl_get_formats(struct gud_drm_gadget *gdg, __le32 *formats)
+{
+	unsigned int i;
+
+	for (i = 0; i < gdg->format_count; i++)
+		formats[i] = cpu_to_le32(gdg->formats[i]);
+}
+
+static int gud_drm_gadget_ctrl_get_connector(struct gud_drm_gadget *gdg, unsigned int index,
+					     struct gud_drm_req_get_connector *desc)
+{
+	struct gud_drm_gadget_connector *gconn;
+
+	if (index >= gdg->connector_count)
+		return -EINVAL;
+
+	memset(desc, 0, sizeof(*desc));
+
+	gconn = &gdg->connectors[index];
+
+	desc->connector_type = gconn->connector->connector_type;
+	desc->flags = cpu_to_le32(GUD_DRM_CONNECTOR_FLAGS_POLL);
+	desc->num_properties = gconn->num_properties;
+
+	return 0;
+}
+
+static struct gud_drm_gadget_connector *
+gud_drm_gadget_get_gconn(struct gud_drm_gadget *gdg, unsigned int index)
+{
+	if (index >= gdg->connector_count)
+		return NULL;
+
+	return &gdg->connectors[index];
+}
+
+static int gud_drm_gadget_ctrl_get_connector_status(struct gud_drm_gadget *gdg, unsigned int index,
+						    struct gud_drm_req_get_connector_status *status)
+{
+	struct gud_drm_gadget_connector *gconn;
+	unsigned long flags;
+
+	gconn = gud_drm_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -ENOENT;
+
+	memset(status, 0, sizeof(*status));
+
+	spin_lock_irqsave(&gconn->lock, flags);
+	status->status = gconn->status;
+	if (gconn->changed) {
+		status->status |= GUD_DRM_CONNECTOR_STATUS_CHANGED;
+		gconn->changed = false;
+	}
+	if (gconn->num_modes)
+		status->num_modes = cpu_to_le16(gconn->num_modes);
+	if (gconn->edid_len)
+		status->edid_len = cpu_to_le16(gconn->edid_len);
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	return 0;
+}
+
+/* This runs in interrupt context */
+int gud_drm_gadget_ctrl_get(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size)
+{
+	struct gud_drm_gadget_connector *gconn;
+	unsigned long flags;
+	int ret = -EINVAL;
+
+	pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	if (!size)
+		goto out;
+
+	switch (request) {
+	case USB_REQ_GET_DESCRIPTOR:
+		ret = gud_drm_gadget_ctrl_get_display_descriptor(gdg, index, data, size);
+		break;
+	case GUD_DRM_USB_REQ_GET_FORMATS:
+		if (!index && size == gdg->format_count * sizeof(u32)) {
+			gud_drm_gadget_ctrl_get_formats(gdg, data);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_PROPERTIES:
+		if (!index && size == gdg->num_properties * sizeof(*gdg->properties)) {
+			memcpy(data, gdg->properties, size);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR:
+		if (size == sizeof(struct gud_drm_req_get_connector))
+			ret = gud_drm_gadget_ctrl_get_connector(gdg, index, data);
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_PROPERTIES:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		if (gconn && size == gconn->num_properties * sizeof(*gconn->properties)) {
+			memcpy(data, gconn->properties, size);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_TV_MODE_VALUES:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		if (gconn && size == gconn->num_tv_mode_enum_names * DRM_PROP_NAME_LEN) {
+			memcpy(data, gconn->tv_mode_enum_names, size);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_STATUS:
+		if (size == sizeof(struct gud_drm_req_get_connector_status))
+			ret = gud_drm_gadget_ctrl_get_connector_status(gdg, index, data);
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_MODES:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		spin_lock_irqsave(&gconn->lock, flags);
+		if (gconn && size == gconn->num_modes * sizeof(*gconn->modes)) {
+			memcpy(data, gconn->modes, size);
+			ret = 0;
+		}
+		spin_unlock_irqrestore(&gconn->lock, flags);
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_EDID:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		spin_lock_irqsave(&gconn->lock, flags);
+		if (gconn && size == gconn->edid_len) {
+			memcpy(data, gconn->edid, size);
+			ret = 0;
+		}
+		spin_unlock_irqrestore(&gconn->lock, flags);
+		break;
+	}
+out:
+	refcount_dec(&gdg->usecnt);
+
+	return !ret ? size : ret;
+}
+EXPORT_SYMBOL(gud_drm_gadget_ctrl_get);
+
+int gud_drm_gadget_ctrl_set(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size)
+{
+	struct gud_drm_gadget_connector *gconn;
+	int dpms, ret = -EINVAL;
+
+	pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	switch (request) {
+	case GUD_DRM_USB_REQ_SET_CONNECTOR_FORCE_DETECT:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		if (gconn)
+			ret = gud_drm_gadget_probe_connector(gconn);
+		break;
+	case GUD_DRM_USB_REQ_SET_STATE_CHECK:
+		ret = gud_drm_gadget_check(gdg, data, size);
+		break;
+	case GUD_DRM_USB_REQ_SET_STATE_COMMIT:
+		if (!size)
+			ret = gud_drm_gadget_commit(gdg);
+		break;
+	case GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE:
+		if (size == sizeof(u8)) {
+			if (*(u8 *)data == 0)
+				ret = gud_drm_gadget_disable_pipe(gdg);
+			else
+				ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE:
+		if (size == sizeof(u8)) {
+			dpms = *(u8 *)data ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF;
+			ret = drm_client_modeset_dpms(&gdg->client, dpms);
+		}
+		break;
+	}
+
+	refcount_dec(&gdg->usecnt);
+
+	return ret;
+}
+EXPORT_SYMBOL(gud_drm_gadget_ctrl_set);
+
+static int gud_drm_gadget_get_formats(struct gud_drm_gadget *gdg, u8 *max_cpp)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct drm_plane *plane;
+	unsigned int i;
+	u32 *formats;
+	int ret;
+
+	*max_cpp = 0;
+
+	drm_for_each_plane(plane, drm) {
+		if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+			break;
+	}
+
+	formats = kcalloc(plane->format_count, sizeof(u32), GFP_KERNEL);
+	if (!formats)
+		return -ENOMEM;
+
+	for (i = 0; i < plane->format_count; i++) {
+		const struct drm_format_info *fmt;
+
+		fmt = drm_format_info(plane->format_types[i]);
+		if (fmt->num_planes != 1)
+			continue;
+
+		/*
+		 * Supporting 24-bit bpp would add complexity so don't bother.
+		 * It's hardly used and compression removes much of the gain.
+		 */
+		if (fmt->cpp[0] == 3)
+			continue;
+
+		if (*max_cpp < fmt->cpp[0])
+			*max_cpp = fmt->cpp[0];
+
+		formats[gdg->format_count++] = plane->format_types[i];
+	}
+
+	if (!gdg->format_count) {
+		ret = -ENOENT;
+		goto err_free;
+	}
+
+	gdg->formats = formats;
+
+	return 0;
+
+err_free:
+	kfree(formats);
+
+	return ret;
+}
+
+static int gud_drm_gadget_get_rotation_property(struct drm_device *drm, u16 *prop, u64 *val)
+{
+	struct drm_property_enum *prop_enum;
+	struct drm_plane *plane;
+	unsigned int num_props = 0;
+	u16 bitmask = 0;
+
+	drm_for_each_plane(plane, drm) {
+		if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+			break;
+	}
+
+	if (!plane->rotation_property)
+		return 0;
+
+	list_for_each_entry(prop_enum, &plane->rotation_property->enum_list, head) {
+		num_props++;
+		bitmask |= BIT(prop_enum->value);
+	}
+
+	*prop = GUD_DRM_PROPERTY_ROTATION;
+	*val = bitmask;
+
+	return 1;
+}
+
+static int gud_drm_gadget_get_properties(struct gud_drm_gadget *gdg)
+{
+	struct gud_drm_property *properties;
+	u16 prop;
+	u64 val;
+	int ret;
+
+	ret = gud_drm_gadget_get_rotation_property(gdg->client.dev, &prop, &val);
+	if (ret <= 0)
+		return ret;
+
+	properties = kcalloc(1, sizeof(*properties), GFP_KERNEL);
+	if (!properties)
+		return -ENOMEM;
+
+	gdg->properties = properties;
+	gdg->num_properties++;
+
+	properties[0].prop = cpu_to_le16(prop);
+	properties[0].val = cpu_to_le64(val);
+
+	return 0;
+}
+
+static int gud_drm_gadget_get_connector_properties(struct gud_drm_gadget *gdg,
+						   struct gud_drm_gadget_connector *gconn)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct drm_mode_config *config = &drm->mode_config;
+	struct drm_connector *connector = gconn->connector;
+	struct drm_object_properties *conn_props = connector->base.properties;
+	struct gud_drm_property *properties;
+	unsigned int i, ret = 0;
+	u16 prop;
+	u64 val;
+
+	mutex_lock(&drm->mode_config.mutex);
+
+	if (!conn_props->count)
+		goto unlock;
+
+	/* Add room for possible backlight */
+	properties = kcalloc(conn_props->count + 1, sizeof(*properties), GFP_KERNEL);
+	if (!properties) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	gconn->properties = properties;
+
+	for (i = 0; i < conn_props->count; i++) {
+		struct drm_property *property = conn_props->properties[i];
+
+		if (property == config->tv_select_subconnector_property) {
+			prop = GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR;
+			val = connector->state->tv.subconnector;
+		} else if (property == config->tv_left_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_LEFT_MARGIN;
+			val = connector->state->tv.margins.left;
+		} else if (property == config->tv_right_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_RIGHT_MARGIN;
+			val = connector->state->tv.margins.right;
+		} else if (property == config->tv_top_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_TOP_MARGIN;
+			val = connector->state->tv.margins.top;
+		} else if (property == config->tv_bottom_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN;
+			val = connector->state->tv.margins.bottom;
+		} else if (property == config->tv_mode_property) {
+			struct drm_property_enum *prop_enum;
+			char *buf;
+
+			list_for_each_entry(prop_enum, &property->enum_list, head)
+				gconn->num_tv_mode_enum_names++;
+
+			if (WARN_ON(!gconn->num_tv_mode_enum_names)) {
+				ret = -EINVAL;
+				goto unlock;
+			}
+
+			buf = kcalloc(gconn->num_tv_mode_enum_names, DRM_PROP_NAME_LEN, GFP_KERNEL);
+			if (!buf) {
+				ret = -ENOMEM;
+				goto unlock;
+			}
+
+			gconn->tv_mode_enum_names = buf;
+
+			list_for_each_entry(prop_enum, &property->enum_list, head) {
+				strncpy(buf, prop_enum->name, DRM_PROP_NAME_LEN);
+				buf += DRM_PROP_NAME_LEN;
+			}
+
+			prop = GUD_DRM_PROPERTY_TV_MODE;
+			val = connector->state->tv.mode;
+			val |= gconn->num_tv_mode_enum_names << GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT;
+		} else if (property == config->tv_brightness_property) {
+			prop = GUD_DRM_PROPERTY_TV_BRIGHTNESS;
+			val = connector->state->tv.brightness;
+		} else if (property == config->tv_contrast_property) {
+			prop = GUD_DRM_PROPERTY_TV_CONTRAST;
+			val = connector->state->tv.contrast;
+		} else if (property == config->tv_flicker_reduction_property) {
+			prop = GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION;
+			val = connector->state->tv.flicker_reduction;
+		} else if (property == config->tv_overscan_property) {
+			prop = GUD_DRM_PROPERTY_TV_OVERSCAN;
+			val = connector->state->tv.overscan;
+		} else if (property == config->tv_saturation_property) {
+			prop = GUD_DRM_PROPERTY_TV_SATURATION;
+			val = connector->state->tv.saturation;
+		} else if (property == config->tv_hue_property) {
+			prop = GUD_DRM_PROPERTY_TV_HUE;
+			val = connector->state->tv.hue;
+		} else {
+			continue;
+		}
+
+		properties[gconn->num_properties].prop = cpu_to_le16(prop);
+		properties[gconn->num_properties++].val = cpu_to_le64(val);
+	}
+
+	if (!connector->index && gdg->backlight) {
+		struct backlight_properties *props = &gdg->backlight->props;
+
+		prop = GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS;
+		val = DIV64_U64_ROUND_UP(props->brightness * 100, props->max_brightness);
+		properties[gconn->num_properties].prop = cpu_to_le16(prop);
+		properties[gconn->num_properties++].val = cpu_to_le64(val);
+		gconn->backlight = gdg->backlight;
+	}
+unlock:
+	mutex_unlock(&drm->mode_config.mutex);
+
+	return ret;
+}
+
+static int gud_drm_gadget_get_connectors(struct gud_drm_gadget *gdg)
+{
+	struct gud_drm_gadget_connector *connectors = NULL;
+	struct drm_connector_list_iter conn_iter;
+	struct drm_device *drm = gdg->client.dev;
+	unsigned int connector_count = 0;
+	struct drm_connector *connector;
+	int ret = 0;
+
+	drm_connector_list_iter_begin(drm, &conn_iter);
+	drm_client_for_each_connector_iter(connector, &conn_iter) {
+		struct gud_drm_gadget_connector *tmp, *gconn;
+
+		tmp = krealloc(connectors, (connector_count + 1) * sizeof(*connectors),
+			       GFP_KERNEL | __GFP_ZERO);
+		if (!tmp) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		connectors = tmp;
+		drm_connector_get(connector);
+		gconn = &connectors[connector_count++];
+		gconn->connector = connector;
+		spin_lock_init(&gconn->lock);
+
+		ret = gud_drm_gadget_get_connector_properties(gdg, gconn);
+		if (ret)
+			break;
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	gdg->connectors = connectors;
+	gdg->connector_count = connector_count;
+
+	return ret;
+}
+
+static void gud_drm_gadget_release(struct kref *kref)
+{
+	struct gud_drm_gadget *gdg = container_of(kref, struct gud_drm_gadget, refcount);
+
+	kfree(gdg);
+}
+
+static void gud_drm_gadget_put(struct gud_drm_gadget *gdg)
+{
+	kref_put(&gdg->refcount, gud_drm_gadget_release);
+}
+
+static void gud_drm_gadget_client_unregister(struct drm_client_dev *client)
+{
+	struct gud_drm_gadget *gdg = container_of(client, struct gud_drm_gadget, client);
+	int timeout = 10000 / 50;
+	unsigned int i;
+
+	/*
+	 * If usecnt doesn't drop to zero, try waiting for the gadget, but we
+	 * can't block the DRM driver forever. The worst case wait the gadget side
+	 * can hit are tens of seconds through the call to drm_client_modeset_commit().
+	 */
+	if (refcount_dec_and_test(&gdg->usecnt)) {
+		for (; timeout && refcount_read(&gdg->usecnt); timeout--)
+			msleep(50);
+	}
+
+	if (!timeout) {
+		pr_err("%s: Timeout waiting for gadget side, will leak memory\n", __func__);
+		return;
+	}
+
+	vfree(gdg->work_buf);
+	kfree(gdg->formats);
+	kfree(gdg->properties);
+
+	for (i = 0; i < gdg->connector_count; i++) {
+		struct gud_drm_gadget_connector *gconn = &gdg->connectors[i];
+
+		drm_connector_put(gconn->connector);
+		kfree(gconn->properties);
+		kfree(gconn->tv_mode_enum_names);
+		kfree(gconn->modes);
+		kfree(gconn->edid);
+	}
+	kfree(gdg->connectors);
+
+	gud_drm_gadget_delete_buffers(gdg);
+	drm_client_release(client);
+	gud_drm_gadget_put(gdg);
+}
+
+static int gud_drm_gadget_client_hotplug(struct drm_client_dev *client)
+{
+	struct gud_drm_gadget *gdg = container_of(client, struct gud_drm_gadget, client);
+
+	gud_drm_gadget_probe_connectors(gdg);
+
+	return 0;
+}
+
+static const struct drm_client_funcs gdg_client_funcs = {
+	.owner		= THIS_MODULE,
+	.unregister	= gud_drm_gadget_client_unregister,
+	.hotplug	= gud_drm_gadget_client_hotplug,
+};
+
+struct gud_drm_gadget *gud_drm_gadget_init(unsigned int minor_id, const char *backlight,
+					   size_t *max_buffer_size)
+{
+	struct gud_drm_gadget *gdg;
+	u8 max_cpp;
+	int ret;
+
+	gdg = kzalloc(sizeof(*gdg), GFP_KERNEL);
+	if (!gdg)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_client_init_from_id(minor_id, &gdg->client, "gud-drm-gadget", &gdg_client_funcs);
+	if (ret) {
+		pr_err("Failed to aquire minor=%u\n", minor_id);
+		kfree(gdg);
+		return ERR_PTR(ret);
+	}
+
+	refcount_set(&gdg->usecnt, 1);
+	/* The DRM driver (through the client) and f_gud_drm need one ref each */
+	kref_init(&gdg->refcount);
+	kref_get(&gdg->refcount);
+
+	if (backlight) {
+		gdg->backlight = backlight_device_get_by_name(backlight);
+		if (!gdg->backlight) {
+			pr_err("Failed to find backlight: %s\n", backlight);
+			ret = -ENODEV;
+			goto error_release;
+		}
+	}
+
+	ret = gud_drm_gadget_get_formats(gdg, &max_cpp);
+	if (ret) {
+		pr_err("Failed to get formats\n");
+		goto error_release;
+	}
+
+	*max_buffer_size = gdg->client.dev->mode_config.max_width *
+			   gdg->client.dev->mode_config.max_height * max_cpp;
+	/* f_gud_drm will kmalloc a buffer of this size */
+	*max_buffer_size = min_t(size_t, *max_buffer_size, KMALLOC_MAX_SIZE);
+
+	gdg->max_buffer_size = *max_buffer_size;
+	gdg->work_buf = vmalloc(gdg->max_buffer_size);
+	if (!gdg->work_buf) {
+		ret = -ENOMEM;
+		goto error_release;
+	}
+
+	ret = gud_drm_gadget_get_properties(gdg);
+	if (ret) {
+		pr_err("Failed to get properties\n");
+		goto error_release;
+	}
+
+	ret = gud_drm_gadget_get_connectors(gdg);
+	if (ret) {
+		pr_err("Failed to get connectors\n");
+		goto error_release;
+	}
+
+	if (!drm_client_register(&gdg->client)) {
+		pr_err("DRM device is gone\n");
+		ret = -ENODEV;
+		goto error_release;
+	}
+
+	gud_drm_gadget_probe_connectors(gdg);
+
+	return gdg;
+
+error_release:
+	gud_drm_gadget_client_unregister(&gdg->client);
+	gud_drm_gadget_fini(gdg);
+
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(gud_drm_gadget_init);
+
+void gud_drm_gadget_fini(struct gud_drm_gadget *gdg)
+{
+	backlight_put(gdg->backlight);
+	gud_drm_gadget_put(gdg);
+}
+EXPORT_SYMBOL(gud_drm_gadget_fini);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/gud_drm.h b/include/drm/gud_drm.h
index 15bb30577b57..6afc369274d0 100644
--- a/include/drm/gud_drm.h
+++ b/include/drm/gud_drm.h
@@ -353,4 +353,17 @@ static inline void gud_drm_to_display_mode(struct drm_display_mode *dst,
 	drm_mode_set_name(dst);
 }
 
+struct gud_drm_gadget;
+
+struct gud_drm_gadget *gud_drm_gadget_init(unsigned int minor_id, const char *backlight,
+					   size_t *max_buffer_size);
+void gud_drm_gadget_fini(struct gud_drm_gadget *gdg);
+int gud_drm_gadget_disable_pipe(struct gud_drm_gadget *gdg);
+int gud_drm_gadget_ctrl_get(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size);
+int gud_drm_gadget_ctrl_set(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size);
+int gud_drm_gadget_set_buffer(struct gud_drm_gadget *gdg, struct gud_drm_req_set_buffer *req);
+int gud_drm_gadget_write_buffer(struct gud_drm_gadget *gdg, const void *buf, size_t len);
+
 #endif
-- 
2.23.0


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

* [PATCH 09/10] drm/gud: Add functionality for the USB gadget side
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb

Since the USB gadget/device has to reach into the DRM internals, part of
the code is placed in the DRM subsystem as a separate module. All calls
into this module runs in process context and are serialized, except one
function that runs in interrupt context.

Since both the gadget side and the DRM side can disable the gadget,
special care has been taken to make that safe.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/gud/Kconfig          |    6 +
 drivers/gpu/drm/gud/Makefile         |    1 +
 drivers/gpu/drm/gud/gud_drm_gadget.c | 1169 ++++++++++++++++++++++++++
 include/drm/gud_drm.h                |   13 +
 4 files changed, 1189 insertions(+)
 create mode 100644 drivers/gpu/drm/gud/gud_drm_gadget.c

diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig
index 203d4490f1c7..99bfbfbf9023 100644
--- a/drivers/gpu/drm/gud/Kconfig
+++ b/drivers/gpu/drm/gud/Kconfig
@@ -12,3 +12,9 @@ config DRM_GUD
 	  adapters.
 
 	  If M is selected the module will be called gud_drm.
+
+config DRM_GUD_GADGET
+	tristate
+	depends on DRM
+	select LZ4_DECOMPRESS
+	select BACKLIGHT_CLASS_DEVICE
diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile
index 73ed7ef3da94..fb5cfbd93d0f 100644
--- a/drivers/gpu/drm/gud/Makefile
+++ b/drivers/gpu/drm/gud/Makefile
@@ -2,3 +2,4 @@
 
 gud_drm-objs			:= gud_drm_drv.o gud_drm_pipe.o gud_drm_connector.o
 obj-$(CONFIG_DRM_GUD)		+= gud_drm.o
+obj-$(CONFIG_DRM_GUD_GADGET)	+= gud_drm_gadget.o
diff --git a/drivers/gpu/drm/gud/gud_drm_gadget.c b/drivers/gpu/drm/gud/gud_drm_gadget.c
new file mode 100644
index 000000000000..a9c916abec85
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_drm_gadget.c
@@ -0,0 +1,1169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/lz4.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_mode_object.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_rect.h>
+#include <drm/gud_drm.h>
+
+#include "gud_drm_internal.h"
+
+/*
+ * Concurrency:
+ * Calls into this module from f_gud_drm are serialized and run in process
+ * context except gud_drm_gadget_ctrl_get() which is run in interrupt context.
+ *
+ * Termination:
+ * A DRM client can not release itself, only the DRM driver which resources the
+ * client uses can do that.
+ * This means that there are 2 paths to stop the gadget function:
+ * - Unregistering the DRM driver (module unload)
+ * - Disabling the USB gadget (configfs unbind)
+ *
+ * A use counter protects the gadget should the client go away. A kref is used
+ * to control the release of the gud_drm_gadget structure shared by the 2 actors.
+ *
+ * Backlight:
+ * If there's a backlight device it's attached to the first connector.
+ */
+
+struct gud_drm_gadget_connector {
+	struct drm_connector *connector;
+
+	const struct gud_drm_property *properties;
+	unsigned int num_properties;
+	const char *tv_mode_enum_names;
+	unsigned int num_tv_mode_enum_names;
+	struct backlight_device *backlight;
+
+	spinlock_t lock; /* Protects the following members: */
+	enum drm_connector_status status;
+	unsigned int width_mm;
+	unsigned int height_mm;
+	struct gud_drm_display_mode *modes;
+	unsigned int num_modes;
+	void *edid;
+	size_t edid_len;
+	bool changed;
+};
+
+struct gud_drm_gadget {
+	struct kref refcount;
+	refcount_t usecnt;
+	struct drm_client_dev client;
+	struct backlight_device *backlight;
+
+	const u32 *formats;
+	unsigned int format_count;
+
+	const struct gud_drm_property *properties;
+	unsigned int num_properties;
+
+	struct gud_drm_gadget_connector *connectors;
+	unsigned int connector_count;
+
+	struct drm_rect set_buffer_rect;
+	u32 set_buffer_length;
+	u8 set_buffer_compression;
+	u32 set_buffer_compressed_length;
+
+	struct drm_client_buffer *buffer;
+	struct drm_client_buffer *buffer_check;
+	u8 brightness;
+	bool check_ok;
+
+	size_t max_buffer_size;
+	void *work_buf;
+};
+
+static int gud_drm_gadget_probe_connector(struct gud_drm_gadget_connector *gconn)
+{
+	struct drm_connector *connector = gconn->connector;
+	struct gud_drm_display_mode *modes = NULL;
+	struct drm_device *drm = connector->dev;
+	struct drm_display_mode *mode;
+	void *edid_data, *edid = NULL;
+	unsigned int num_modes = 0;
+	size_t edid_len = 0;
+	unsigned long flags;
+	unsigned int i = 0;
+	int ret = 0;
+
+	mutex_lock(&drm->mode_config.mutex);
+
+	connector->funcs->fill_modes(connector,
+				     drm->mode_config.max_width,
+				     drm->mode_config.max_height);
+
+	list_for_each_entry(mode, &connector->modes, head)
+		num_modes++;
+
+	if (!num_modes)
+		goto update;
+
+	modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL);
+	if (!modes) {
+		ret = -ENOMEM;
+		num_modes = 0;
+		goto update;
+	}
+
+	list_for_each_entry(mode, &connector->modes, head)
+		gud_drm_from_display_mode(&modes[i++], mode);
+
+	if (!connector->edid_blob_ptr)
+		goto update;
+
+	edid_data = connector->edid_blob_ptr->data;
+	edid_len = connector->edid_blob_ptr->length;
+	if (!edid_data || !edid_len) {
+		edid_len = 0;
+		goto update;
+	}
+
+	edid = kmemdup(edid_data, edid_len, GFP_KERNEL);
+	if (!edid) {
+		ret = -ENOMEM;
+		edid_len = 0;
+	}
+
+update:
+	spin_lock_irqsave(&gconn->lock, flags);
+	if (gconn->status != connector->status || gconn->num_modes != num_modes ||
+	    gconn->edid_len != edid_len ||
+	    (gconn->modes && modes && memcmp(gconn->modes, modes, num_modes * sizeof(*modes))) ||
+	    (gconn->edid && edid && memcmp(gconn->edid, edid, edid_len)))
+		gconn->changed = true;
+	swap(gconn->modes, modes);
+	gconn->num_modes = num_modes;
+	swap(gconn->edid, edid);
+	gconn->edid_len = edid_len;
+	gconn->width_mm = connector->display_info.width_mm;
+	gconn->height_mm = connector->display_info.height_mm;
+	gconn->status = connector->status;
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	mutex_unlock(&drm->mode_config.mutex);
+
+	kfree(edid);
+	kfree(modes);
+
+	return ret;
+}
+
+static void gud_drm_gadget_probe_connectors(struct gud_drm_gadget *gdg)
+{
+	unsigned int i;
+
+	for (i = 0; i < gdg->connector_count; i++)
+		gud_drm_gadget_probe_connector(&gdg->connectors[i]);
+}
+
+static bool gud_drm_gadget_check_buffer(struct gud_drm_gadget *gdg,
+					struct drm_client_buffer *buffer,
+					struct drm_display_mode *mode,
+					u32 format)
+{
+	struct drm_framebuffer *fb;
+
+	if (!buffer)
+		return false;
+
+	fb = buffer->fb;
+
+	return fb->format->format == format &&
+	       fb->width == mode->hdisplay && fb->height == mode->vdisplay;
+}
+
+static bool gud_drm_gadget_set_connector_property(struct drm_client_dev *client,
+						  struct drm_connector *connector,
+						  u16 prop, u64 val, int *ret)
+{
+	struct drm_mode_config *config = &connector->dev->mode_config;
+	struct drm_property *property;
+
+	switch (prop) {
+	case GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR:
+		property = config->tv_select_subconnector_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_LEFT_MARGIN:
+		property = config->tv_left_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_RIGHT_MARGIN:
+		property = config->tv_right_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_TOP_MARGIN:
+		property = config->tv_top_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN:
+		property = config->tv_bottom_margin_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_MODE:
+		property = config->tv_mode_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_BRIGHTNESS:
+		property = config->tv_brightness_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_CONTRAST:
+		property = config->tv_contrast_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION:
+		property = config->tv_flicker_reduction_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_OVERSCAN:
+		property = config->tv_overscan_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_SATURATION:
+		property = config->tv_saturation_property;
+		break;
+	case GUD_DRM_PROPERTY_TV_HUE:
+		property = config->tv_hue_property;
+		break;
+	default:
+		return false;
+	}
+
+	*ret = drm_client_modeset_set_property(client, &connector->base, property, val);
+
+	return true;
+}
+
+static int gud_drm_gadget_check(struct gud_drm_gadget *gdg, struct gud_drm_req_set_state *req,
+				size_t size)
+{
+	struct drm_client_dev *client = &gdg->client;
+	u32 format = le32_to_cpu(req->format);
+	struct drm_client_buffer *buffer;
+	struct drm_connector *connector;
+	struct drm_display_mode mode;
+	unsigned int i;
+	void *vaddr;
+	int ret;
+
+	if (size < sizeof(struct gud_drm_req_set_state))
+		return -EINVAL;
+
+	if (size != struct_size(req, properties, req->num_properties))
+		return -EINVAL;
+
+	memset(&mode, 0, sizeof(mode));
+	gud_drm_to_display_mode(&mode, &req->mode);
+
+	gdg->check_ok = false;
+
+	if (!mode.hdisplay || !format)
+		return -EINVAL;
+
+	if (req->connector >= gdg->connector_count)
+		return -EINVAL;
+
+	connector = gdg->connectors[req->connector].connector;
+
+	if (gdg->buffer_check) {
+		drm_client_framebuffer_delete(gdg->buffer_check);
+		gdg->buffer_check = NULL;
+	}
+
+	if (!gud_drm_gadget_check_buffer(gdg, gdg->buffer, &mode, format)) {
+		buffer = drm_client_framebuffer_create(client, mode.hdisplay, mode.vdisplay,
+						       format);
+		if (IS_ERR(buffer))
+			return PTR_ERR(buffer);
+
+		vaddr = drm_client_buffer_vmap(buffer);
+		if (IS_ERR(vaddr)) {
+			drm_client_framebuffer_delete(buffer);
+			return PTR_ERR(vaddr);
+		}
+
+		gdg->buffer_check = buffer;
+	} else {
+		buffer = gdg->buffer;
+	}
+
+	ret = drm_client_modeset_set(client, connector, &mode, buffer->fb);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < req->num_properties; i++) {
+		u16 prop = le16_to_cpu(req->properties[i].prop);
+		u64 val = le64_to_cpu(req->properties[i].val);
+
+		if (gud_drm_gadget_set_connector_property(client, connector, prop, val, &ret)) {
+			if (ret)
+				return ret;
+			continue;
+		}
+
+		switch (prop) {
+		case GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS:
+			if (val > 100)
+				return -EINVAL;
+			gdg->brightness = val;
+			break;
+		case GUD_DRM_PROPERTY_ROTATION:
+			ret = drm_client_modeset_set_rotation(client, val);
+			break;
+		default:
+			pr_err("%s: Unknown property: %u\n", __func__, prop);
+			continue;
+		}
+
+		if (ret)
+			return ret;
+	}
+
+	ret = drm_client_modeset_check(&gdg->client);
+	if (ret)
+		return ret;
+
+	gdg->check_ok = true;
+
+	return 0;
+}
+
+static int gud_drm_gadget_commit(struct gud_drm_gadget *gdg)
+{
+	int ret;
+
+	if (!gdg->check_ok)
+		return -EINVAL;
+
+	if (gdg->backlight) {
+		int val, max_brightness = gdg->backlight->props.max_brightness;
+
+		val = DIV64_U64_ROUND_UP(gdg->brightness * max_brightness, 100);
+		ret = backlight_device_set_brightness(gdg->backlight, val);
+		if (ret)
+			return ret;
+	}
+
+	ret = drm_client_modeset_commit(&gdg->client);
+	if (ret)
+		return ret;
+
+	if (gdg->buffer_check) {
+		drm_client_framebuffer_delete(gdg->buffer);
+		gdg->buffer = gdg->buffer_check;
+		gdg->buffer_check = NULL;
+	}
+
+	return 0;
+}
+
+static size_t gud_drm_gadget_write_buffer_memcpy(struct drm_client_buffer *buffer,
+						 const void *src, size_t len,
+						 struct drm_rect *rect)
+{
+	unsigned int cpp = buffer->fb->format->cpp[0];
+	size_t dst_pitch = buffer->fb->pitches[0];
+	size_t src_pitch = drm_rect_width(rect) * cpp;
+	unsigned int y;
+	void *dst;
+
+	/* Get the address, it's already mapped */
+	dst = drm_client_buffer_vmap(buffer);
+	dst += rect->y1 * dst_pitch;
+	dst += rect->x1 * cpp;
+
+	for (y = 0; y < drm_rect_height(rect) && len; y++) {
+		src_pitch = min(src_pitch, len);
+		memcpy(dst, src, src_pitch);
+		src += src_pitch;
+		dst += dst_pitch;
+		len -= src_pitch;
+	}
+
+	return len;
+}
+
+static bool gud_drm_gadget_check_rect(struct drm_client_buffer *buffer, struct drm_rect *rect)
+{
+	return buffer->fb && rect->x1 < rect->x2 && rect->y1 < rect->y2 &&
+	       rect->x2 <= buffer->fb->width && rect->y2 <= buffer->fb->height;
+}
+
+int gud_drm_gadget_write_buffer(struct gud_drm_gadget *gdg, const void *buf, size_t len)
+{
+	struct drm_client_buffer *buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check;
+	struct drm_rect *rect = &gdg->set_buffer_rect;
+	u8 compression = gdg->set_buffer_compression;
+	struct drm_framebuffer *fb;
+	size_t remain;
+	int ret;
+
+	pr_debug("%s: len=%zu compression=0x%x\n", __func__, len, compression);
+
+	if (WARN_ON_ONCE(!buffer))
+		return -ENOMEM;
+
+	if (!gud_drm_gadget_check_rect(buffer, rect)) {
+		pr_err("%s: Rectangle doesn't fit\n", __func__);
+		return -EINVAL;
+	}
+
+	fb = buffer->fb;
+
+	if (compression & GUD_DRM_COMPRESSION_LZ4) {
+		if (len != gdg->set_buffer_compressed_length) {
+			pr_err("%s: Buffer compressed len differs: %zu != %zu\n",
+			       __func__, len, gdg->set_buffer_compressed_length);
+			return -EINVAL;
+		}
+
+		ret = LZ4_decompress_safe(buf, gdg->work_buf, len, gdg->max_buffer_size);
+		if (ret < 0) {
+			pr_err("%s: Failed to decompress buffer\n", __func__);
+			return -EIO;
+		}
+
+		buf = gdg->work_buf;
+		len = ret;
+	}
+
+	if (len != gdg->set_buffer_length) {
+		pr_err("%s: Buffer len differs: %zu != %zu\n",
+		       __func__, len, gdg->set_buffer_length);
+		return -EINVAL;
+	}
+
+	if (len > (drm_rect_width(rect) * drm_rect_height(rect) * fb->format->cpp[0])) {
+		pr_err("%s: Buffer is too big for rectangle\n", __func__);
+		return -EINVAL;
+	}
+
+	remain = gud_drm_gadget_write_buffer_memcpy(buffer, buf, len, rect);
+	if (remain) {
+		pr_err("%s: Failed to write buffer: remain=%zu\n", __func__, remain);
+		return -EIO;
+	}
+
+	return drm_client_framebuffer_flush(buffer, rect);
+}
+EXPORT_SYMBOL(gud_drm_gadget_write_buffer);
+
+int gud_drm_gadget_set_buffer(struct gud_drm_gadget *gdg, struct gud_drm_req_set_buffer *req)
+{
+	u32 compressed_length = le32_to_cpu(req->compressed_length);
+	u32 length = le32_to_cpu(req->length);
+	struct drm_client_buffer *buffer;
+	struct drm_rect rect;
+	int ret = 0;
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check;
+	if (!buffer) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	drm_rect_init(&rect, le32_to_cpu(req->x), le32_to_cpu(req->y),
+		      le32_to_cpu(req->width), le32_to_cpu(req->height));
+
+	pr_debug("%s: " DRM_RECT_FMT "\n", __func__, DRM_RECT_ARG(&rect));
+
+	if (!gud_drm_gadget_check_rect(buffer, &rect)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (req->compression & ~GUD_DRM_COMPRESSION_LZ4) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	gdg->set_buffer_rect = rect;
+	gdg->set_buffer_length = length;
+
+	if (req->compression) {
+		if (!compressed_length) {
+			ret = -EINVAL;
+			goto out;
+		}
+		gdg->set_buffer_compression = req->compression;
+		gdg->set_buffer_compressed_length = compressed_length;
+		length = compressed_length;
+	} else {
+		gdg->set_buffer_compression = 0;
+		gdg->set_buffer_compressed_length = 0;
+	}
+out:
+	refcount_dec(&gdg->usecnt);
+
+	return ret ? ret : length;
+}
+EXPORT_SYMBOL(gud_drm_gadget_set_buffer);
+
+static void gud_drm_gadget_delete_buffers(struct gud_drm_gadget *gdg)
+{
+	drm_client_framebuffer_delete(gdg->buffer_check);
+	drm_client_framebuffer_delete(gdg->buffer);
+	gdg->buffer_check = NULL;
+	gdg->buffer = NULL;
+}
+
+int gud_drm_gadget_disable_pipe(struct gud_drm_gadget *gdg)
+{
+	int ret;
+
+	drm_client_modeset_set(&gdg->client, NULL, NULL, NULL);
+	ret = drm_client_modeset_commit(&gdg->client);
+	if (ret)
+		return ret;
+
+	gud_drm_gadget_delete_buffers(gdg);
+
+	return 0;
+}
+EXPORT_SYMBOL(gud_drm_gadget_disable_pipe);
+
+static int gud_drm_gadget_ctrl_get_display_descriptor(struct gud_drm_gadget *gdg, u16 value,
+						      void *data, size_t size)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct gud_drm_display_descriptor desc;
+	u8 type = value >> 8;
+	u8 index = value & 0xff;
+
+	if (type != GUD_DRM_USB_DT_DISPLAY || index)
+		return -EINVAL;
+
+	desc.bLength = sizeof(desc);
+	desc.bDescriptorType = GUD_DRM_USB_DT_DISPLAY;
+
+	desc.bVersion = 1;
+	desc.bMaxBufferSizeOrder = ilog2(gdg->max_buffer_size);
+	desc.bmFlags = 0;
+	desc.bCompression = GUD_DRM_COMPRESSION_LZ4;
+
+	desc.dwMinWidth = cpu_to_le32(drm->mode_config.min_width);
+	desc.dwMaxWidth = cpu_to_le32(drm->mode_config.max_width);
+	desc.dwMinHeight = cpu_to_le32(drm->mode_config.min_height);
+	desc.dwMaxHeight = cpu_to_le32(drm->mode_config.max_height);
+	desc.bNumFormats = gdg->format_count;
+	desc.bNumProperties = gdg->num_properties;
+	desc.bNumConnectors = gdg->connector_count;
+
+	size = min(size, sizeof(desc));
+	memcpy(data, &desc, size);
+
+	return size;
+}
+
+static void gud_drm_gadget_ctrl_get_formats(struct gud_drm_gadget *gdg, __le32 *formats)
+{
+	unsigned int i;
+
+	for (i = 0; i < gdg->format_count; i++)
+		formats[i] = cpu_to_le32(gdg->formats[i]);
+}
+
+static int gud_drm_gadget_ctrl_get_connector(struct gud_drm_gadget *gdg, unsigned int index,
+					     struct gud_drm_req_get_connector *desc)
+{
+	struct gud_drm_gadget_connector *gconn;
+
+	if (index >= gdg->connector_count)
+		return -EINVAL;
+
+	memset(desc, 0, sizeof(*desc));
+
+	gconn = &gdg->connectors[index];
+
+	desc->connector_type = gconn->connector->connector_type;
+	desc->flags = cpu_to_le32(GUD_DRM_CONNECTOR_FLAGS_POLL);
+	desc->num_properties = gconn->num_properties;
+
+	return 0;
+}
+
+static struct gud_drm_gadget_connector *
+gud_drm_gadget_get_gconn(struct gud_drm_gadget *gdg, unsigned int index)
+{
+	if (index >= gdg->connector_count)
+		return NULL;
+
+	return &gdg->connectors[index];
+}
+
+static int gud_drm_gadget_ctrl_get_connector_status(struct gud_drm_gadget *gdg, unsigned int index,
+						    struct gud_drm_req_get_connector_status *status)
+{
+	struct gud_drm_gadget_connector *gconn;
+	unsigned long flags;
+
+	gconn = gud_drm_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -ENOENT;
+
+	memset(status, 0, sizeof(*status));
+
+	spin_lock_irqsave(&gconn->lock, flags);
+	status->status = gconn->status;
+	if (gconn->changed) {
+		status->status |= GUD_DRM_CONNECTOR_STATUS_CHANGED;
+		gconn->changed = false;
+	}
+	if (gconn->num_modes)
+		status->num_modes = cpu_to_le16(gconn->num_modes);
+	if (gconn->edid_len)
+		status->edid_len = cpu_to_le16(gconn->edid_len);
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	return 0;
+}
+
+/* This runs in interrupt context */
+int gud_drm_gadget_ctrl_get(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size)
+{
+	struct gud_drm_gadget_connector *gconn;
+	unsigned long flags;
+	int ret = -EINVAL;
+
+	pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	if (!size)
+		goto out;
+
+	switch (request) {
+	case USB_REQ_GET_DESCRIPTOR:
+		ret = gud_drm_gadget_ctrl_get_display_descriptor(gdg, index, data, size);
+		break;
+	case GUD_DRM_USB_REQ_GET_FORMATS:
+		if (!index && size == gdg->format_count * sizeof(u32)) {
+			gud_drm_gadget_ctrl_get_formats(gdg, data);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_PROPERTIES:
+		if (!index && size == gdg->num_properties * sizeof(*gdg->properties)) {
+			memcpy(data, gdg->properties, size);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR:
+		if (size == sizeof(struct gud_drm_req_get_connector))
+			ret = gud_drm_gadget_ctrl_get_connector(gdg, index, data);
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_PROPERTIES:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		if (gconn && size == gconn->num_properties * sizeof(*gconn->properties)) {
+			memcpy(data, gconn->properties, size);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_TV_MODE_VALUES:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		if (gconn && size == gconn->num_tv_mode_enum_names * DRM_PROP_NAME_LEN) {
+			memcpy(data, gconn->tv_mode_enum_names, size);
+			ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_STATUS:
+		if (size == sizeof(struct gud_drm_req_get_connector_status))
+			ret = gud_drm_gadget_ctrl_get_connector_status(gdg, index, data);
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_MODES:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		spin_lock_irqsave(&gconn->lock, flags);
+		if (gconn && size == gconn->num_modes * sizeof(*gconn->modes)) {
+			memcpy(data, gconn->modes, size);
+			ret = 0;
+		}
+		spin_unlock_irqrestore(&gconn->lock, flags);
+		break;
+	case GUD_DRM_USB_REQ_GET_CONNECTOR_EDID:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		spin_lock_irqsave(&gconn->lock, flags);
+		if (gconn && size == gconn->edid_len) {
+			memcpy(data, gconn->edid, size);
+			ret = 0;
+		}
+		spin_unlock_irqrestore(&gconn->lock, flags);
+		break;
+	}
+out:
+	refcount_dec(&gdg->usecnt);
+
+	return !ret ? size : ret;
+}
+EXPORT_SYMBOL(gud_drm_gadget_ctrl_get);
+
+int gud_drm_gadget_ctrl_set(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size)
+{
+	struct gud_drm_gadget_connector *gconn;
+	int dpms, ret = -EINVAL;
+
+	pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	switch (request) {
+	case GUD_DRM_USB_REQ_SET_CONNECTOR_FORCE_DETECT:
+		gconn = gud_drm_gadget_get_gconn(gdg, index);
+		if (gconn)
+			ret = gud_drm_gadget_probe_connector(gconn);
+		break;
+	case GUD_DRM_USB_REQ_SET_STATE_CHECK:
+		ret = gud_drm_gadget_check(gdg, data, size);
+		break;
+	case GUD_DRM_USB_REQ_SET_STATE_COMMIT:
+		if (!size)
+			ret = gud_drm_gadget_commit(gdg);
+		break;
+	case GUD_DRM_USB_REQ_SET_CONTROLLER_ENABLE:
+		if (size == sizeof(u8)) {
+			if (*(u8 *)data == 0)
+				ret = gud_drm_gadget_disable_pipe(gdg);
+			else
+				ret = 0;
+		}
+		break;
+	case GUD_DRM_USB_REQ_SET_DISPLAY_ENABLE:
+		if (size == sizeof(u8)) {
+			dpms = *(u8 *)data ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF;
+			ret = drm_client_modeset_dpms(&gdg->client, dpms);
+		}
+		break;
+	}
+
+	refcount_dec(&gdg->usecnt);
+
+	return ret;
+}
+EXPORT_SYMBOL(gud_drm_gadget_ctrl_set);
+
+static int gud_drm_gadget_get_formats(struct gud_drm_gadget *gdg, u8 *max_cpp)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct drm_plane *plane;
+	unsigned int i;
+	u32 *formats;
+	int ret;
+
+	*max_cpp = 0;
+
+	drm_for_each_plane(plane, drm) {
+		if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+			break;
+	}
+
+	formats = kcalloc(plane->format_count, sizeof(u32), GFP_KERNEL);
+	if (!formats)
+		return -ENOMEM;
+
+	for (i = 0; i < plane->format_count; i++) {
+		const struct drm_format_info *fmt;
+
+		fmt = drm_format_info(plane->format_types[i]);
+		if (fmt->num_planes != 1)
+			continue;
+
+		/*
+		 * Supporting 24-bit bpp would add complexity so don't bother.
+		 * It's hardly used and compression removes much of the gain.
+		 */
+		if (fmt->cpp[0] == 3)
+			continue;
+
+		if (*max_cpp < fmt->cpp[0])
+			*max_cpp = fmt->cpp[0];
+
+		formats[gdg->format_count++] = plane->format_types[i];
+	}
+
+	if (!gdg->format_count) {
+		ret = -ENOENT;
+		goto err_free;
+	}
+
+	gdg->formats = formats;
+
+	return 0;
+
+err_free:
+	kfree(formats);
+
+	return ret;
+}
+
+static int gud_drm_gadget_get_rotation_property(struct drm_device *drm, u16 *prop, u64 *val)
+{
+	struct drm_property_enum *prop_enum;
+	struct drm_plane *plane;
+	unsigned int num_props = 0;
+	u16 bitmask = 0;
+
+	drm_for_each_plane(plane, drm) {
+		if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+			break;
+	}
+
+	if (!plane->rotation_property)
+		return 0;
+
+	list_for_each_entry(prop_enum, &plane->rotation_property->enum_list, head) {
+		num_props++;
+		bitmask |= BIT(prop_enum->value);
+	}
+
+	*prop = GUD_DRM_PROPERTY_ROTATION;
+	*val = bitmask;
+
+	return 1;
+}
+
+static int gud_drm_gadget_get_properties(struct gud_drm_gadget *gdg)
+{
+	struct gud_drm_property *properties;
+	u16 prop;
+	u64 val;
+	int ret;
+
+	ret = gud_drm_gadget_get_rotation_property(gdg->client.dev, &prop, &val);
+	if (ret <= 0)
+		return ret;
+
+	properties = kcalloc(1, sizeof(*properties), GFP_KERNEL);
+	if (!properties)
+		return -ENOMEM;
+
+	gdg->properties = properties;
+	gdg->num_properties++;
+
+	properties[0].prop = cpu_to_le16(prop);
+	properties[0].val = cpu_to_le64(val);
+
+	return 0;
+}
+
+static int gud_drm_gadget_get_connector_properties(struct gud_drm_gadget *gdg,
+						   struct gud_drm_gadget_connector *gconn)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct drm_mode_config *config = &drm->mode_config;
+	struct drm_connector *connector = gconn->connector;
+	struct drm_object_properties *conn_props = connector->base.properties;
+	struct gud_drm_property *properties;
+	unsigned int i, ret = 0;
+	u16 prop;
+	u64 val;
+
+	mutex_lock(&drm->mode_config.mutex);
+
+	if (!conn_props->count)
+		goto unlock;
+
+	/* Add room for possible backlight */
+	properties = kcalloc(conn_props->count + 1, sizeof(*properties), GFP_KERNEL);
+	if (!properties) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	gconn->properties = properties;
+
+	for (i = 0; i < conn_props->count; i++) {
+		struct drm_property *property = conn_props->properties[i];
+
+		if (property == config->tv_select_subconnector_property) {
+			prop = GUD_DRM_PROPERTY_TV_SELECT_SUBCONNECTOR;
+			val = connector->state->tv.subconnector;
+		} else if (property == config->tv_left_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_LEFT_MARGIN;
+			val = connector->state->tv.margins.left;
+		} else if (property == config->tv_right_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_RIGHT_MARGIN;
+			val = connector->state->tv.margins.right;
+		} else if (property == config->tv_top_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_TOP_MARGIN;
+			val = connector->state->tv.margins.top;
+		} else if (property == config->tv_bottom_margin_property) {
+			prop = GUD_DRM_PROPERTY_TV_BOTTOM_MARGIN;
+			val = connector->state->tv.margins.bottom;
+		} else if (property == config->tv_mode_property) {
+			struct drm_property_enum *prop_enum;
+			char *buf;
+
+			list_for_each_entry(prop_enum, &property->enum_list, head)
+				gconn->num_tv_mode_enum_names++;
+
+			if (WARN_ON(!gconn->num_tv_mode_enum_names)) {
+				ret = -EINVAL;
+				goto unlock;
+			}
+
+			buf = kcalloc(gconn->num_tv_mode_enum_names, DRM_PROP_NAME_LEN, GFP_KERNEL);
+			if (!buf) {
+				ret = -ENOMEM;
+				goto unlock;
+			}
+
+			gconn->tv_mode_enum_names = buf;
+
+			list_for_each_entry(prop_enum, &property->enum_list, head) {
+				strncpy(buf, prop_enum->name, DRM_PROP_NAME_LEN);
+				buf += DRM_PROP_NAME_LEN;
+			}
+
+			prop = GUD_DRM_PROPERTY_TV_MODE;
+			val = connector->state->tv.mode;
+			val |= gconn->num_tv_mode_enum_names << GUD_DRM_USB_CONNECTOR_TV_MODE_NUM_SHIFT;
+		} else if (property == config->tv_brightness_property) {
+			prop = GUD_DRM_PROPERTY_TV_BRIGHTNESS;
+			val = connector->state->tv.brightness;
+		} else if (property == config->tv_contrast_property) {
+			prop = GUD_DRM_PROPERTY_TV_CONTRAST;
+			val = connector->state->tv.contrast;
+		} else if (property == config->tv_flicker_reduction_property) {
+			prop = GUD_DRM_PROPERTY_TV_FLICKER_REDUCTION;
+			val = connector->state->tv.flicker_reduction;
+		} else if (property == config->tv_overscan_property) {
+			prop = GUD_DRM_PROPERTY_TV_OVERSCAN;
+			val = connector->state->tv.overscan;
+		} else if (property == config->tv_saturation_property) {
+			prop = GUD_DRM_PROPERTY_TV_SATURATION;
+			val = connector->state->tv.saturation;
+		} else if (property == config->tv_hue_property) {
+			prop = GUD_DRM_PROPERTY_TV_HUE;
+			val = connector->state->tv.hue;
+		} else {
+			continue;
+		}
+
+		properties[gconn->num_properties].prop = cpu_to_le16(prop);
+		properties[gconn->num_properties++].val = cpu_to_le64(val);
+	}
+
+	if (!connector->index && gdg->backlight) {
+		struct backlight_properties *props = &gdg->backlight->props;
+
+		prop = GUD_DRM_PROPERTY_BACKLIGHT_BRIGHTNESS;
+		val = DIV64_U64_ROUND_UP(props->brightness * 100, props->max_brightness);
+		properties[gconn->num_properties].prop = cpu_to_le16(prop);
+		properties[gconn->num_properties++].val = cpu_to_le64(val);
+		gconn->backlight = gdg->backlight;
+	}
+unlock:
+	mutex_unlock(&drm->mode_config.mutex);
+
+	return ret;
+}
+
+static int gud_drm_gadget_get_connectors(struct gud_drm_gadget *gdg)
+{
+	struct gud_drm_gadget_connector *connectors = NULL;
+	struct drm_connector_list_iter conn_iter;
+	struct drm_device *drm = gdg->client.dev;
+	unsigned int connector_count = 0;
+	struct drm_connector *connector;
+	int ret = 0;
+
+	drm_connector_list_iter_begin(drm, &conn_iter);
+	drm_client_for_each_connector_iter(connector, &conn_iter) {
+		struct gud_drm_gadget_connector *tmp, *gconn;
+
+		tmp = krealloc(connectors, (connector_count + 1) * sizeof(*connectors),
+			       GFP_KERNEL | __GFP_ZERO);
+		if (!tmp) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		connectors = tmp;
+		drm_connector_get(connector);
+		gconn = &connectors[connector_count++];
+		gconn->connector = connector;
+		spin_lock_init(&gconn->lock);
+
+		ret = gud_drm_gadget_get_connector_properties(gdg, gconn);
+		if (ret)
+			break;
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	gdg->connectors = connectors;
+	gdg->connector_count = connector_count;
+
+	return ret;
+}
+
+static void gud_drm_gadget_release(struct kref *kref)
+{
+	struct gud_drm_gadget *gdg = container_of(kref, struct gud_drm_gadget, refcount);
+
+	kfree(gdg);
+}
+
+static void gud_drm_gadget_put(struct gud_drm_gadget *gdg)
+{
+	kref_put(&gdg->refcount, gud_drm_gadget_release);
+}
+
+static void gud_drm_gadget_client_unregister(struct drm_client_dev *client)
+{
+	struct gud_drm_gadget *gdg = container_of(client, struct gud_drm_gadget, client);
+	int timeout = 10000 / 50;
+	unsigned int i;
+
+	/*
+	 * If usecnt doesn't drop to zero, try waiting for the gadget, but we
+	 * can't block the DRM driver forever. The worst case wait the gadget side
+	 * can hit are tens of seconds through the call to drm_client_modeset_commit().
+	 */
+	if (refcount_dec_and_test(&gdg->usecnt)) {
+		for (; timeout && refcount_read(&gdg->usecnt); timeout--)
+			msleep(50);
+	}
+
+	if (!timeout) {
+		pr_err("%s: Timeout waiting for gadget side, will leak memory\n", __func__);
+		return;
+	}
+
+	vfree(gdg->work_buf);
+	kfree(gdg->formats);
+	kfree(gdg->properties);
+
+	for (i = 0; i < gdg->connector_count; i++) {
+		struct gud_drm_gadget_connector *gconn = &gdg->connectors[i];
+
+		drm_connector_put(gconn->connector);
+		kfree(gconn->properties);
+		kfree(gconn->tv_mode_enum_names);
+		kfree(gconn->modes);
+		kfree(gconn->edid);
+	}
+	kfree(gdg->connectors);
+
+	gud_drm_gadget_delete_buffers(gdg);
+	drm_client_release(client);
+	gud_drm_gadget_put(gdg);
+}
+
+static int gud_drm_gadget_client_hotplug(struct drm_client_dev *client)
+{
+	struct gud_drm_gadget *gdg = container_of(client, struct gud_drm_gadget, client);
+
+	gud_drm_gadget_probe_connectors(gdg);
+
+	return 0;
+}
+
+static const struct drm_client_funcs gdg_client_funcs = {
+	.owner		= THIS_MODULE,
+	.unregister	= gud_drm_gadget_client_unregister,
+	.hotplug	= gud_drm_gadget_client_hotplug,
+};
+
+struct gud_drm_gadget *gud_drm_gadget_init(unsigned int minor_id, const char *backlight,
+					   size_t *max_buffer_size)
+{
+	struct gud_drm_gadget *gdg;
+	u8 max_cpp;
+	int ret;
+
+	gdg = kzalloc(sizeof(*gdg), GFP_KERNEL);
+	if (!gdg)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_client_init_from_id(minor_id, &gdg->client, "gud-drm-gadget", &gdg_client_funcs);
+	if (ret) {
+		pr_err("Failed to aquire minor=%u\n", minor_id);
+		kfree(gdg);
+		return ERR_PTR(ret);
+	}
+
+	refcount_set(&gdg->usecnt, 1);
+	/* The DRM driver (through the client) and f_gud_drm need one ref each */
+	kref_init(&gdg->refcount);
+	kref_get(&gdg->refcount);
+
+	if (backlight) {
+		gdg->backlight = backlight_device_get_by_name(backlight);
+		if (!gdg->backlight) {
+			pr_err("Failed to find backlight: %s\n", backlight);
+			ret = -ENODEV;
+			goto error_release;
+		}
+	}
+
+	ret = gud_drm_gadget_get_formats(gdg, &max_cpp);
+	if (ret) {
+		pr_err("Failed to get formats\n");
+		goto error_release;
+	}
+
+	*max_buffer_size = gdg->client.dev->mode_config.max_width *
+			   gdg->client.dev->mode_config.max_height * max_cpp;
+	/* f_gud_drm will kmalloc a buffer of this size */
+	*max_buffer_size = min_t(size_t, *max_buffer_size, KMALLOC_MAX_SIZE);
+
+	gdg->max_buffer_size = *max_buffer_size;
+	gdg->work_buf = vmalloc(gdg->max_buffer_size);
+	if (!gdg->work_buf) {
+		ret = -ENOMEM;
+		goto error_release;
+	}
+
+	ret = gud_drm_gadget_get_properties(gdg);
+	if (ret) {
+		pr_err("Failed to get properties\n");
+		goto error_release;
+	}
+
+	ret = gud_drm_gadget_get_connectors(gdg);
+	if (ret) {
+		pr_err("Failed to get connectors\n");
+		goto error_release;
+	}
+
+	if (!drm_client_register(&gdg->client)) {
+		pr_err("DRM device is gone\n");
+		ret = -ENODEV;
+		goto error_release;
+	}
+
+	gud_drm_gadget_probe_connectors(gdg);
+
+	return gdg;
+
+error_release:
+	gud_drm_gadget_client_unregister(&gdg->client);
+	gud_drm_gadget_fini(gdg);
+
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(gud_drm_gadget_init);
+
+void gud_drm_gadget_fini(struct gud_drm_gadget *gdg)
+{
+	backlight_put(gdg->backlight);
+	gud_drm_gadget_put(gdg);
+}
+EXPORT_SYMBOL(gud_drm_gadget_fini);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/gud_drm.h b/include/drm/gud_drm.h
index 15bb30577b57..6afc369274d0 100644
--- a/include/drm/gud_drm.h
+++ b/include/drm/gud_drm.h
@@ -353,4 +353,17 @@ static inline void gud_drm_to_display_mode(struct drm_display_mode *dst,
 	drm_mode_set_name(dst);
 }
 
+struct gud_drm_gadget;
+
+struct gud_drm_gadget *gud_drm_gadget_init(unsigned int minor_id, const char *backlight,
+					   size_t *max_buffer_size);
+void gud_drm_gadget_fini(struct gud_drm_gadget *gdg);
+int gud_drm_gadget_disable_pipe(struct gud_drm_gadget *gdg);
+int gud_drm_gadget_ctrl_get(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size);
+int gud_drm_gadget_ctrl_set(struct gud_drm_gadget *gdg, u8 request,
+			    u16 index, void *data, size_t size);
+int gud_drm_gadget_set_buffer(struct gud_drm_gadget *gdg, struct gud_drm_req_set_buffer *req);
+int gud_drm_gadget_write_buffer(struct gud_drm_gadget *gdg, const void *buf, size_t len);
+
 #endif
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH 10/10] usb: gadget: function: Add Generic USB Display support
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-04-29 12:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Noralf Trønnes, Felipe Balbi

This adds the gadget side support for the Generic USB Display. It presents
a DRM display device as a USB Display configured through configfs.

The display is implemented as a vendor type USB interface with one bulk
out endpoint. The protocol is implemented using control requests.
lz4 compressed framebuffer data/pixels are sent over the bulk endpoint.

The DRM part of the gadget is placed in the DRM subsystem since it reaches
into the DRM internals.

Cc: Felipe Balbi <balbi@kernel.org>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 .../ABI/testing/configfs-usb-gadget-gud_drm   |  10 +
 MAINTAINERS                                   |   2 +
 drivers/usb/gadget/Kconfig                    |  12 +
 drivers/usb/gadget/function/Makefile          |   2 +
 drivers/usb/gadget/function/f_gud_drm.c       | 678 ++++++++++++++++++
 5 files changed, 704 insertions(+)
 create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-gud_drm
 create mode 100644 drivers/usb/gadget/function/f_gud_drm.c

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-gud_drm b/Documentation/ABI/testing/configfs-usb-gadget-gud_drm
new file mode 100644
index 000000000000..d6d802c16eeb
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-usb-gadget-gud_drm
@@ -0,0 +1,10 @@
+What:		/config/usb-gadget/gadget/functions/gud_drm.name
+Date:		Apr 2020
+KernelVersion:	5.8
+Description:
+		The attributes:
+
+		drm_dev - DRM device number
+		backlight_dev - Backlight device name (optional)
+				The backlight brightness scale should be
+				perceptual not linear.
diff --git a/MAINTAINERS b/MAINTAINERS
index bb4609984e9a..fe56c68beb11 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5287,7 +5287,9 @@ M:	Noralf Trønnes <noralf@tronnes.org>
 S:	Maintained
 W:	https://github.com/notro/gud/wiki
 T:	git git://anongit.freedesktop.org/drm/drm-misc
+F:	Documentation/ABI/testing/configfs-usb-gadget-gud_drm
 F:	drivers/gpu/drm/gud/
+F:	drivers/usb/gadget/function/f_gud_drm.c
 F:	include/drm/gud_drm.h
 
 DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index c6db0a0a340c..8d90add495b6 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -216,6 +216,9 @@ config USB_F_PRINTER
 config USB_F_TCM
 	tristate
 
+config USB_F_GUD_DRM
+	tristate
+
 # this first set of drivers all depend on bulk-capable hardware.
 
 config USB_CONFIGFS
@@ -483,6 +486,15 @@ config USB_CONFIGFS_F_TCM
 	  Both protocols can work on USB2.0 and USB3.0.
 	  UAS utilizes the USB 3.0 feature called streams support.
 
+config USB_CONFIGFS_F_GUD_DRM
+	bool "Generic USB Display Gadget function"
+	depends on USB_CONFIGFS
+	depends on DRM
+	select DRM_GUD_GADGET
+	select USB_F_GUD_DRM
+	help
+	  This presents a DRM display device as a Generic USB Display.
+
 source "drivers/usb/gadget/legacy/Kconfig"
 
 endif # USB_GADGET
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 5d3a6cf02218..cd71caa2a34c 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -50,3 +50,5 @@ usb_f_printer-y			:= f_printer.o
 obj-$(CONFIG_USB_F_PRINTER)	+= usb_f_printer.o
 usb_f_tcm-y			:= f_tcm.o
 obj-$(CONFIG_USB_F_TCM)		+= usb_f_tcm.o
+usb_f_gud_drm-y			:= f_gud_drm.o
+obj-$(CONFIG_USB_F_GUD_DRM)	+= usb_f_gud_drm.o
diff --git a/drivers/usb/gadget/function/f_gud_drm.c b/drivers/usb/gadget/function/f_gud_drm.c
new file mode 100644
index 000000000000..9a2d6bb9739f
--- /dev/null
+++ b/drivers/usb/gadget/function/f_gud_drm.c
@@ -0,0 +1,678 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Noralf Trønnes
+ */
+
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/workqueue.h>
+
+#include <drm/gud_drm.h>
+
+struct f_gud_drm {
+	struct usb_function func;
+	struct work_struct worker;
+	size_t max_buffer_size;
+	void *ctrl_req_buf;
+
+	u8 interface_id;
+	struct usb_request *ctrl_req;
+
+	struct usb_ep *bulk_ep;
+	struct usb_request *bulk_req;
+
+	struct gud_drm_gadget *gdg;
+
+	spinlock_t lock; /* Protects the following members: */
+	bool ctrl_pending;
+	bool status_pending;
+	bool bulk_pending;
+	bool disable_pending;
+	u8 errno;
+	u16 request;
+	u16 value;
+};
+
+static inline struct f_gud_drm *func_to_f_gud_drm(struct usb_function *f)
+{
+	return container_of(f, struct f_gud_drm, func);
+}
+
+struct f_gud_drm_opts {
+	struct usb_function_instance func_inst;
+	struct mutex lock;
+	int refcnt;
+
+	unsigned int drm_dev;
+	const char *backlight_dev;
+};
+
+static inline struct f_gud_drm_opts *fi_to_f_gud_drm_opts(const struct usb_function_instance *fi)
+{
+	return container_of(fi, struct f_gud_drm_opts, func_inst);
+}
+
+static inline struct f_gud_drm_opts *ci_to_f_gud_drm_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_gud_drm_opts,
+			    func_inst.group);
+}
+
+#define F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(name, addr, size)	\
+	static struct usb_endpoint_descriptor name = {		\
+		.bLength =		USB_DT_ENDPOINT_SIZE,	\
+		.bDescriptorType =	USB_DT_ENDPOINT,	\
+		.bEndpointAddress =	addr,			\
+		.bmAttributes =		USB_ENDPOINT_XFER_BULK,	\
+		.wMaxPacketSize =	cpu_to_le16(size),	\
+	}
+
+static struct usb_interface_descriptor f_gud_drm_intf = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_drm_fs_out_desc, USB_DIR_OUT, 0);
+
+static struct usb_descriptor_header *f_gud_drm_fs_function[] = {
+	(struct usb_descriptor_header *)&f_gud_drm_intf,
+	(struct usb_descriptor_header *)&f_gud_drm_fs_out_desc,
+	NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_drm_hs_out_desc, USB_DIR_OUT, 512);
+
+static struct usb_descriptor_header *f_gud_drm_hs_function[] = {
+	(struct usb_descriptor_header *)&f_gud_drm_intf,
+	(struct usb_descriptor_header *)&f_gud_drm_hs_out_desc,
+	NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_drm_ss_out_desc, USB_DIR_OUT, 1024);
+
+static struct usb_ss_ep_comp_descriptor f_gud_drm_ss_bulk_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *f_gud_drm_ss_function[] = {
+	(struct usb_descriptor_header *)&f_gud_drm_intf,
+	(struct usb_descriptor_header *)&f_gud_drm_ss_out_desc,
+	(struct usb_descriptor_header *)&f_gud_drm_ss_bulk_comp_desc,
+	NULL,
+};
+
+static struct usb_string f_gud_drm_string_defs[] = {
+	[0].s = "Generic USB Display",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings f_gud_drm_string_table = {
+	.language =	0x0409,	/* en-us */
+	.strings =	f_gud_drm_string_defs,
+};
+
+static struct usb_gadget_strings *f_gud_drm_strings[] = {
+	&f_gud_drm_string_table,
+	NULL,
+};
+
+static void f_gud_drm_bulk_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_gud_drm *fgd = req->context;
+	unsigned long flags;
+
+	if (req->status || req->actual != req->length)
+		return;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->bulk_pending = true;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	queue_work(system_long_wq, &fgd->worker);
+}
+
+static int f_gud_drm_ctrl_req_set_buffer(struct f_gud_drm *fgd, void *buf, size_t len)
+{
+	int ret;
+
+	if (len != sizeof(struct gud_drm_req_set_buffer))
+		return -EINVAL;
+
+	ret = gud_drm_gadget_set_buffer(fgd->gdg, buf);
+	if (ret < 0)
+		return ret;
+
+	if (ret > fgd->max_buffer_size)
+		return -EOVERFLOW;
+
+	fgd->bulk_req->length = ret;
+
+	return usb_ep_queue(fgd->bulk_ep, fgd->bulk_req, GFP_KERNEL);
+}
+
+static void f_gud_drm_worker(struct work_struct *work)
+{
+	struct f_gud_drm *fgd = container_of(work, struct f_gud_drm, worker);
+	bool ctrl_pending, bulk_pending, disable_pending;
+	struct gud_drm_gadget *gdg = fgd->gdg;
+	unsigned long flags;
+	u16 request, value;
+	int ret;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	request = fgd->request;
+	value = fgd->value;
+	ctrl_pending = fgd->ctrl_pending;
+	bulk_pending = fgd->bulk_pending;
+	disable_pending = fgd->disable_pending;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	pr_debug("%s: bulk_pending=%u ctrl_pending=%u disable_pending=%u\n",
+		 __func__, bulk_pending, ctrl_pending, disable_pending);
+
+	if (disable_pending) {
+		gud_drm_gadget_disable_pipe(gdg);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->disable_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+		return;
+	}
+
+	if (bulk_pending) {
+		struct usb_request *req = fgd->bulk_req;
+
+		ret = gud_drm_gadget_write_buffer(gdg, req->buf, req->actual);
+		if (ret)
+			pr_err("%s: Failed to write buffer, error=%d\n", __func__, ret);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->bulk_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	}
+
+	if (ctrl_pending) {
+		unsigned int length = fgd->ctrl_req->length;
+		void *buf = fgd->ctrl_req->buf;
+
+		if (request == GUD_DRM_USB_REQ_SET_BUFFER)
+			ret = f_gud_drm_ctrl_req_set_buffer(fgd, buf, length);
+		else
+			ret = gud_drm_gadget_ctrl_set(gdg, request, value, buf, length);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		if (!fgd->errno) /* Don't scribble over an EBUSY or ESHUTDOWN */
+			fgd->errno = -ret;
+		fgd->ctrl_pending = false;
+		fgd->status_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	}
+}
+
+static void f_gud_drm_ctrl_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_gud_drm *fgd = req->context;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+
+	if (req->status)
+		ret = req->status;
+	else if (req->actual != req->length)
+		ret = -EREMOTEIO;
+	if (ret) {
+		fgd->errno = -ret;
+		fgd->status_pending = false;
+	} else {
+		fgd->ctrl_pending = true;
+	}
+
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	if (!ret)
+		queue_work(system_long_wq, &fgd->worker);
+}
+
+static int f_gud_drm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	bool in = ctrl->bRequestType & USB_DIR_IN;
+	u16 length = le16_to_cpu(ctrl->wLength);
+	u16 value = le16_to_cpu(ctrl->wValue);
+	unsigned long flags;
+	int ret;
+
+	if (ctrl->bRequest == USB_REQ_GET_STATUS) {
+		struct gud_drm_req_get_status *status = cdev->req->buf;
+
+		if (!in || length != sizeof(*status))
+			return -EINVAL;
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		status->flags = 0;
+		if (fgd->status_pending)
+			status->flags |= GUD_DRM_STATUS_PENDING;
+		status->errno = fgd->errno;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	} else if (in) {
+		if (length > USB_COMP_EP0_BUFSIZ) /* 4k */
+			return -EOVERFLOW;
+
+		ret = gud_drm_gadget_ctrl_get(fgd->gdg, ctrl->bRequest, value,
+					      cdev->req->buf, length);
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->status_pending = false;
+		fgd->errno = ret < 0 ? -ret : 0;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+		if (ret < 0)
+			return ret;
+
+		length = ret;
+	} else {
+		if (length > GUD_DRM_MAX_TRANSFER_SIZE)
+			return -EOVERFLOW;
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		if (fgd->ctrl_pending) {
+			/* If we get here the host has timed out on the previous request */
+			ret = -EBUSY;
+			fgd->status_pending = false;
+			fgd->errno = -ret;
+		} else {
+			ret = 0;
+			fgd->errno = 0;
+			fgd->request = ctrl->bRequest;
+			fgd->value = value;
+			fgd->status_pending = true;
+		}
+		spin_unlock_irqrestore(&fgd->lock, flags);
+
+		if (ret)
+			return ret;
+
+		fgd->ctrl_req->length = length;
+
+		return usb_ep_queue(cdev->gadget->ep0, fgd->ctrl_req, GFP_ATOMIC);
+	}
+
+	cdev->req->length = length;
+
+	return usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+}
+
+static bool f_gud_drm_req_match(struct usb_function *f, const struct usb_ctrlrequest *ctrl,
+				bool config0)
+{
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+
+	if (config0)
+		return false;
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_VENDOR)
+		return false;
+
+	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
+		return false;
+
+	return fgd->interface_id == le16_to_cpu(ctrl->wIndex);
+}
+
+static int f_gud_drm_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	unsigned long flags;
+
+	if (alt || intf != fgd->interface_id)
+		return -EINVAL;
+
+	if (!fgd->bulk_ep->desc) {
+		pr_debug("%s: init\n", __func__);
+		if (config_ep_by_speed(cdev->gadget, f, fgd->bulk_ep)) {
+			fgd->bulk_ep->desc = NULL;
+			return -EINVAL;
+		}
+	}
+
+	pr_debug("%s: reset\n", __func__);
+
+	usb_ep_disable(fgd->bulk_ep);
+	usb_ep_enable(fgd->bulk_ep);
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->ctrl_pending = false;
+	fgd->bulk_pending = false;
+	fgd->disable_pending = false;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	return 0;
+}
+
+static void f_gud_drm_disable(struct usb_function *f)
+{
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	unsigned long flags;
+
+	pr_debug("%s\n", __func__);
+
+	usb_ep_disable(fgd->bulk_ep);
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->ctrl_pending = false;
+	fgd->bulk_pending = false;
+	fgd->status_pending = false;
+	fgd->disable_pending = true;
+	fgd->errno = ESHUTDOWN;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	queue_work(system_long_wq, &fgd->worker);
+}
+
+static void f_gud_drm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	struct usb_composite_dev *cdev = fgd->func.config->cdev;
+
+	flush_work(&fgd->worker);
+
+	gud_drm_gadget_fini(fgd->gdg);
+	fgd->gdg = NULL;
+
+	kfree(fgd->bulk_req->buf);
+	usb_ep_free_request(fgd->bulk_ep, fgd->bulk_req);
+	usb_ep_free_request(cdev->gadget->ep0, fgd->ctrl_req);
+	fgd->ctrl_req = NULL;
+	fgd->bulk_req = NULL;
+	fgd->bulk_ep = NULL;
+
+	usb_free_all_descriptors(f);
+}
+
+static int f_gud_drm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_gud_drm_opts *opts = fi_to_f_gud_drm_opts(f->fi);
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	struct usb_request *ctrl_req, *bulk_req;
+	struct gud_drm_gadget *gdg;
+	struct usb_string *us;
+	void *buf;
+	int ret;
+
+	us = usb_gstrings_attach(cdev, f_gud_drm_strings,
+				 ARRAY_SIZE(f_gud_drm_string_defs));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+
+	f_gud_drm_intf.iInterface = us[0].id;
+
+	ret = usb_interface_id(c, f);
+	if (ret < 0)
+		return ret;
+
+	fgd->interface_id = ret;
+	f_gud_drm_intf.bInterfaceNumber = fgd->interface_id;
+
+	fgd->bulk_ep = usb_ep_autoconfig(cdev->gadget, &f_gud_drm_fs_out_desc);
+	if (!fgd->bulk_ep)
+		return -ENODEV;
+
+	f_gud_drm_hs_out_desc.bEndpointAddress = f_gud_drm_fs_out_desc.bEndpointAddress;
+
+	f_gud_drm_ss_out_desc.bEndpointAddress = f_gud_drm_fs_out_desc.bEndpointAddress;
+
+	ret = usb_assign_descriptors(f, f_gud_drm_fs_function, f_gud_drm_hs_function,
+				     f_gud_drm_ss_function, NULL);
+	if (ret)
+		return ret;
+
+	ctrl_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
+	if (!ctrl_req) {
+		ret = -ENOMEM;
+		goto fail_free_descs;
+	}
+
+	ctrl_req->buf = fgd->ctrl_req_buf;
+	ctrl_req->complete = f_gud_drm_ctrl_req_complete;
+	ctrl_req->context = fgd;
+
+	gdg = gud_drm_gadget_init(opts->drm_dev, opts->backlight_dev, &fgd->max_buffer_size);
+	if (IS_ERR(gdg)) {
+		ret = PTR_ERR(gdg);
+		goto fail_free_ctrl_req;
+	}
+
+	bulk_req = usb_ep_alloc_request(fgd->bulk_ep, GFP_KERNEL);
+	if (!bulk_req) {
+		ret = -ENOMEM;
+		goto fail_free_ctrl_req;
+	}
+
+	buf = kmalloc(fgd->max_buffer_size, GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto fail_free_bulk_req;
+	}
+
+	bulk_req->complete = f_gud_drm_bulk_complete;
+	bulk_req->context = fgd;
+	bulk_req->buf = buf;
+
+	fgd->ctrl_req = ctrl_req;
+	fgd->bulk_req = bulk_req;
+	fgd->gdg = gdg;
+
+	return 0;
+
+fail_free_bulk_req:
+	usb_ep_free_request(fgd->bulk_ep, bulk_req);
+fail_free_ctrl_req:
+	usb_ep_free_request(cdev->gadget->ep0, ctrl_req);
+fail_free_descs:
+	usb_free_all_descriptors(f);
+
+	return ret;
+}
+
+static void f_gud_drm_free_func(struct usb_function *f)
+{
+	struct f_gud_drm_opts *opts = container_of(f->fi, struct f_gud_drm_opts, func_inst);
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+
+	kfree(fgd->ctrl_req_buf);
+	kfree(fgd);
+}
+
+static struct usb_function *f_gud_drm_alloc_func(struct usb_function_instance *fi)
+{
+	struct f_gud_drm_opts *opts = fi_to_f_gud_drm_opts(fi);
+	struct usb_function *func;
+	struct f_gud_drm *fgd;
+
+	fgd = kzalloc(sizeof(*fgd), GFP_KERNEL);
+	if (!fgd)
+		return ERR_PTR(-ENOMEM);
+
+	fgd->ctrl_req_buf = kmalloc(GUD_DRM_MAX_TRANSFER_SIZE, GFP_KERNEL);
+	if (!fgd->ctrl_req_buf)
+		goto error;
+
+	spin_lock_init(&fgd->lock);
+	INIT_WORK(&fgd->worker, f_gud_drm_worker);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt++;
+	mutex_unlock(&opts->lock);
+
+	func = &fgd->func;
+	func->name = "gud_drm";
+	func->bind = f_gud_drm_bind;
+	func->unbind = f_gud_drm_unbind;
+	func->set_alt = f_gud_drm_set_alt;
+	func->req_match = f_gud_drm_req_match;
+	func->setup = f_gud_drm_setup;
+	func->disable = f_gud_drm_disable;
+	func->free_func = f_gud_drm_free_func;
+
+	return func;
+
+error:
+	kfree(fgd);
+
+	return ERR_PTR(-ENOMEM);
+}
+
+static ssize_t f_gud_drm_opts_drm_dev_show(struct config_item *item, char *page)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->drm_dev);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_gud_drm_opts_drm_dev_store(struct config_item *item,
+					    const char *page, size_t len)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	unsigned int num;
+	int ret;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	ret = kstrtouint(page, 0, &num);
+	if (ret)
+		goto unlock;
+
+	opts->drm_dev = num;
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_drm_opts_, drm_dev);
+
+static ssize_t f_gud_drm_opts_backlight_dev_show(struct config_item *item, char *page)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	ssize_t ret = 0;
+
+	mutex_lock(&opts->lock);
+	if (opts->backlight_dev)
+		ret = strscpy(page, opts->backlight_dev, PAGE_SIZE);
+	else
+		page[0] = '\0';
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+static ssize_t f_gud_drm_opts_backlight_dev_store(struct config_item *item,
+						  const char *page, size_t len)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	ssize_t ret;
+	char *name;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	name = kstrndup(page, len, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	kfree(opts->backlight_dev);
+	opts->backlight_dev = name;
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_drm_opts_, backlight_dev);
+
+static struct configfs_attribute *f_gud_drm_attrs[] = {
+	&f_gud_drm_opts_attr_drm_dev,
+	&f_gud_drm_opts_attr_backlight_dev,
+	NULL,
+};
+
+static void f_gud_drm_attr_release(struct config_item *item)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_gud_drm_item_ops = {
+	.release	= f_gud_drm_attr_release,
+};
+
+static const struct config_item_type f_gud_drm_func_type = {
+	.ct_item_ops	= &f_gud_drm_item_ops,
+	.ct_attrs	= f_gud_drm_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_gud_drm_free_func_inst(struct usb_function_instance *fi)
+{
+	struct f_gud_drm_opts *opts = fi_to_f_gud_drm_opts(fi);
+
+	mutex_destroy(&opts->lock);
+	kfree(opts->backlight_dev);
+	kfree(opts);
+}
+
+static struct usb_function_instance *f_gud_drm_alloc_func_inst(void)
+{
+	struct f_gud_drm_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_gud_drm_free_func_inst;
+
+	config_group_init_type_name(&opts->func_inst.group, "", &f_gud_drm_func_type);
+
+	return &opts->func_inst;
+}
+
+DECLARE_USB_FUNCTION_INIT(gud_drm, f_gud_drm_alloc_func_inst, f_gud_drm_alloc_func);
+
+MODULE_DESCRIPTION("Generic USB Display Gadget");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
-- 
2.23.0


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

* [PATCH 10/10] usb: gadget: function: Add Generic USB Display support
@ 2020-04-29 12:48   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 12:48 UTC (permalink / raw)
  To: dri-devel, linux-usb; +Cc: Felipe Balbi

This adds the gadget side support for the Generic USB Display. It presents
a DRM display device as a USB Display configured through configfs.

The display is implemented as a vendor type USB interface with one bulk
out endpoint. The protocol is implemented using control requests.
lz4 compressed framebuffer data/pixels are sent over the bulk endpoint.

The DRM part of the gadget is placed in the DRM subsystem since it reaches
into the DRM internals.

Cc: Felipe Balbi <balbi@kernel.org>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 .../ABI/testing/configfs-usb-gadget-gud_drm   |  10 +
 MAINTAINERS                                   |   2 +
 drivers/usb/gadget/Kconfig                    |  12 +
 drivers/usb/gadget/function/Makefile          |   2 +
 drivers/usb/gadget/function/f_gud_drm.c       | 678 ++++++++++++++++++
 5 files changed, 704 insertions(+)
 create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-gud_drm
 create mode 100644 drivers/usb/gadget/function/f_gud_drm.c

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-gud_drm b/Documentation/ABI/testing/configfs-usb-gadget-gud_drm
new file mode 100644
index 000000000000..d6d802c16eeb
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-usb-gadget-gud_drm
@@ -0,0 +1,10 @@
+What:		/config/usb-gadget/gadget/functions/gud_drm.name
+Date:		Apr 2020
+KernelVersion:	5.8
+Description:
+		The attributes:
+
+		drm_dev - DRM device number
+		backlight_dev - Backlight device name (optional)
+				The backlight brightness scale should be
+				perceptual not linear.
diff --git a/MAINTAINERS b/MAINTAINERS
index bb4609984e9a..fe56c68beb11 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5287,7 +5287,9 @@ M:	Noralf Trønnes <noralf@tronnes.org>
 S:	Maintained
 W:	https://github.com/notro/gud/wiki
 T:	git git://anongit.freedesktop.org/drm/drm-misc
+F:	Documentation/ABI/testing/configfs-usb-gadget-gud_drm
 F:	drivers/gpu/drm/gud/
+F:	drivers/usb/gadget/function/f_gud_drm.c
 F:	include/drm/gud_drm.h
 
 DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index c6db0a0a340c..8d90add495b6 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -216,6 +216,9 @@ config USB_F_PRINTER
 config USB_F_TCM
 	tristate
 
+config USB_F_GUD_DRM
+	tristate
+
 # this first set of drivers all depend on bulk-capable hardware.
 
 config USB_CONFIGFS
@@ -483,6 +486,15 @@ config USB_CONFIGFS_F_TCM
 	  Both protocols can work on USB2.0 and USB3.0.
 	  UAS utilizes the USB 3.0 feature called streams support.
 
+config USB_CONFIGFS_F_GUD_DRM
+	bool "Generic USB Display Gadget function"
+	depends on USB_CONFIGFS
+	depends on DRM
+	select DRM_GUD_GADGET
+	select USB_F_GUD_DRM
+	help
+	  This presents a DRM display device as a Generic USB Display.
+
 source "drivers/usb/gadget/legacy/Kconfig"
 
 endif # USB_GADGET
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 5d3a6cf02218..cd71caa2a34c 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -50,3 +50,5 @@ usb_f_printer-y			:= f_printer.o
 obj-$(CONFIG_USB_F_PRINTER)	+= usb_f_printer.o
 usb_f_tcm-y			:= f_tcm.o
 obj-$(CONFIG_USB_F_TCM)		+= usb_f_tcm.o
+usb_f_gud_drm-y			:= f_gud_drm.o
+obj-$(CONFIG_USB_F_GUD_DRM)	+= usb_f_gud_drm.o
diff --git a/drivers/usb/gadget/function/f_gud_drm.c b/drivers/usb/gadget/function/f_gud_drm.c
new file mode 100644
index 000000000000..9a2d6bb9739f
--- /dev/null
+++ b/drivers/usb/gadget/function/f_gud_drm.c
@@ -0,0 +1,678 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Noralf Trønnes
+ */
+
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/workqueue.h>
+
+#include <drm/gud_drm.h>
+
+struct f_gud_drm {
+	struct usb_function func;
+	struct work_struct worker;
+	size_t max_buffer_size;
+	void *ctrl_req_buf;
+
+	u8 interface_id;
+	struct usb_request *ctrl_req;
+
+	struct usb_ep *bulk_ep;
+	struct usb_request *bulk_req;
+
+	struct gud_drm_gadget *gdg;
+
+	spinlock_t lock; /* Protects the following members: */
+	bool ctrl_pending;
+	bool status_pending;
+	bool bulk_pending;
+	bool disable_pending;
+	u8 errno;
+	u16 request;
+	u16 value;
+};
+
+static inline struct f_gud_drm *func_to_f_gud_drm(struct usb_function *f)
+{
+	return container_of(f, struct f_gud_drm, func);
+}
+
+struct f_gud_drm_opts {
+	struct usb_function_instance func_inst;
+	struct mutex lock;
+	int refcnt;
+
+	unsigned int drm_dev;
+	const char *backlight_dev;
+};
+
+static inline struct f_gud_drm_opts *fi_to_f_gud_drm_opts(const struct usb_function_instance *fi)
+{
+	return container_of(fi, struct f_gud_drm_opts, func_inst);
+}
+
+static inline struct f_gud_drm_opts *ci_to_f_gud_drm_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_gud_drm_opts,
+			    func_inst.group);
+}
+
+#define F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(name, addr, size)	\
+	static struct usb_endpoint_descriptor name = {		\
+		.bLength =		USB_DT_ENDPOINT_SIZE,	\
+		.bDescriptorType =	USB_DT_ENDPOINT,	\
+		.bEndpointAddress =	addr,			\
+		.bmAttributes =		USB_ENDPOINT_XFER_BULK,	\
+		.wMaxPacketSize =	cpu_to_le16(size),	\
+	}
+
+static struct usb_interface_descriptor f_gud_drm_intf = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_drm_fs_out_desc, USB_DIR_OUT, 0);
+
+static struct usb_descriptor_header *f_gud_drm_fs_function[] = {
+	(struct usb_descriptor_header *)&f_gud_drm_intf,
+	(struct usb_descriptor_header *)&f_gud_drm_fs_out_desc,
+	NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_drm_hs_out_desc, USB_DIR_OUT, 512);
+
+static struct usb_descriptor_header *f_gud_drm_hs_function[] = {
+	(struct usb_descriptor_header *)&f_gud_drm_intf,
+	(struct usb_descriptor_header *)&f_gud_drm_hs_out_desc,
+	NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_drm_ss_out_desc, USB_DIR_OUT, 1024);
+
+static struct usb_ss_ep_comp_descriptor f_gud_drm_ss_bulk_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *f_gud_drm_ss_function[] = {
+	(struct usb_descriptor_header *)&f_gud_drm_intf,
+	(struct usb_descriptor_header *)&f_gud_drm_ss_out_desc,
+	(struct usb_descriptor_header *)&f_gud_drm_ss_bulk_comp_desc,
+	NULL,
+};
+
+static struct usb_string f_gud_drm_string_defs[] = {
+	[0].s = "Generic USB Display",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings f_gud_drm_string_table = {
+	.language =	0x0409,	/* en-us */
+	.strings =	f_gud_drm_string_defs,
+};
+
+static struct usb_gadget_strings *f_gud_drm_strings[] = {
+	&f_gud_drm_string_table,
+	NULL,
+};
+
+static void f_gud_drm_bulk_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_gud_drm *fgd = req->context;
+	unsigned long flags;
+
+	if (req->status || req->actual != req->length)
+		return;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->bulk_pending = true;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	queue_work(system_long_wq, &fgd->worker);
+}
+
+static int f_gud_drm_ctrl_req_set_buffer(struct f_gud_drm *fgd, void *buf, size_t len)
+{
+	int ret;
+
+	if (len != sizeof(struct gud_drm_req_set_buffer))
+		return -EINVAL;
+
+	ret = gud_drm_gadget_set_buffer(fgd->gdg, buf);
+	if (ret < 0)
+		return ret;
+
+	if (ret > fgd->max_buffer_size)
+		return -EOVERFLOW;
+
+	fgd->bulk_req->length = ret;
+
+	return usb_ep_queue(fgd->bulk_ep, fgd->bulk_req, GFP_KERNEL);
+}
+
+static void f_gud_drm_worker(struct work_struct *work)
+{
+	struct f_gud_drm *fgd = container_of(work, struct f_gud_drm, worker);
+	bool ctrl_pending, bulk_pending, disable_pending;
+	struct gud_drm_gadget *gdg = fgd->gdg;
+	unsigned long flags;
+	u16 request, value;
+	int ret;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	request = fgd->request;
+	value = fgd->value;
+	ctrl_pending = fgd->ctrl_pending;
+	bulk_pending = fgd->bulk_pending;
+	disable_pending = fgd->disable_pending;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	pr_debug("%s: bulk_pending=%u ctrl_pending=%u disable_pending=%u\n",
+		 __func__, bulk_pending, ctrl_pending, disable_pending);
+
+	if (disable_pending) {
+		gud_drm_gadget_disable_pipe(gdg);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->disable_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+		return;
+	}
+
+	if (bulk_pending) {
+		struct usb_request *req = fgd->bulk_req;
+
+		ret = gud_drm_gadget_write_buffer(gdg, req->buf, req->actual);
+		if (ret)
+			pr_err("%s: Failed to write buffer, error=%d\n", __func__, ret);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->bulk_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	}
+
+	if (ctrl_pending) {
+		unsigned int length = fgd->ctrl_req->length;
+		void *buf = fgd->ctrl_req->buf;
+
+		if (request == GUD_DRM_USB_REQ_SET_BUFFER)
+			ret = f_gud_drm_ctrl_req_set_buffer(fgd, buf, length);
+		else
+			ret = gud_drm_gadget_ctrl_set(gdg, request, value, buf, length);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		if (!fgd->errno) /* Don't scribble over an EBUSY or ESHUTDOWN */
+			fgd->errno = -ret;
+		fgd->ctrl_pending = false;
+		fgd->status_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	}
+}
+
+static void f_gud_drm_ctrl_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_gud_drm *fgd = req->context;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+
+	if (req->status)
+		ret = req->status;
+	else if (req->actual != req->length)
+		ret = -EREMOTEIO;
+	if (ret) {
+		fgd->errno = -ret;
+		fgd->status_pending = false;
+	} else {
+		fgd->ctrl_pending = true;
+	}
+
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	if (!ret)
+		queue_work(system_long_wq, &fgd->worker);
+}
+
+static int f_gud_drm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	bool in = ctrl->bRequestType & USB_DIR_IN;
+	u16 length = le16_to_cpu(ctrl->wLength);
+	u16 value = le16_to_cpu(ctrl->wValue);
+	unsigned long flags;
+	int ret;
+
+	if (ctrl->bRequest == USB_REQ_GET_STATUS) {
+		struct gud_drm_req_get_status *status = cdev->req->buf;
+
+		if (!in || length != sizeof(*status))
+			return -EINVAL;
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		status->flags = 0;
+		if (fgd->status_pending)
+			status->flags |= GUD_DRM_STATUS_PENDING;
+		status->errno = fgd->errno;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	} else if (in) {
+		if (length > USB_COMP_EP0_BUFSIZ) /* 4k */
+			return -EOVERFLOW;
+
+		ret = gud_drm_gadget_ctrl_get(fgd->gdg, ctrl->bRequest, value,
+					      cdev->req->buf, length);
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->status_pending = false;
+		fgd->errno = ret < 0 ? -ret : 0;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+		if (ret < 0)
+			return ret;
+
+		length = ret;
+	} else {
+		if (length > GUD_DRM_MAX_TRANSFER_SIZE)
+			return -EOVERFLOW;
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		if (fgd->ctrl_pending) {
+			/* If we get here the host has timed out on the previous request */
+			ret = -EBUSY;
+			fgd->status_pending = false;
+			fgd->errno = -ret;
+		} else {
+			ret = 0;
+			fgd->errno = 0;
+			fgd->request = ctrl->bRequest;
+			fgd->value = value;
+			fgd->status_pending = true;
+		}
+		spin_unlock_irqrestore(&fgd->lock, flags);
+
+		if (ret)
+			return ret;
+
+		fgd->ctrl_req->length = length;
+
+		return usb_ep_queue(cdev->gadget->ep0, fgd->ctrl_req, GFP_ATOMIC);
+	}
+
+	cdev->req->length = length;
+
+	return usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+}
+
+static bool f_gud_drm_req_match(struct usb_function *f, const struct usb_ctrlrequest *ctrl,
+				bool config0)
+{
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+
+	if (config0)
+		return false;
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_VENDOR)
+		return false;
+
+	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
+		return false;
+
+	return fgd->interface_id == le16_to_cpu(ctrl->wIndex);
+}
+
+static int f_gud_drm_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	unsigned long flags;
+
+	if (alt || intf != fgd->interface_id)
+		return -EINVAL;
+
+	if (!fgd->bulk_ep->desc) {
+		pr_debug("%s: init\n", __func__);
+		if (config_ep_by_speed(cdev->gadget, f, fgd->bulk_ep)) {
+			fgd->bulk_ep->desc = NULL;
+			return -EINVAL;
+		}
+	}
+
+	pr_debug("%s: reset\n", __func__);
+
+	usb_ep_disable(fgd->bulk_ep);
+	usb_ep_enable(fgd->bulk_ep);
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->ctrl_pending = false;
+	fgd->bulk_pending = false;
+	fgd->disable_pending = false;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	return 0;
+}
+
+static void f_gud_drm_disable(struct usb_function *f)
+{
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	unsigned long flags;
+
+	pr_debug("%s\n", __func__);
+
+	usb_ep_disable(fgd->bulk_ep);
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->ctrl_pending = false;
+	fgd->bulk_pending = false;
+	fgd->status_pending = false;
+	fgd->disable_pending = true;
+	fgd->errno = ESHUTDOWN;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	queue_work(system_long_wq, &fgd->worker);
+}
+
+static void f_gud_drm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	struct usb_composite_dev *cdev = fgd->func.config->cdev;
+
+	flush_work(&fgd->worker);
+
+	gud_drm_gadget_fini(fgd->gdg);
+	fgd->gdg = NULL;
+
+	kfree(fgd->bulk_req->buf);
+	usb_ep_free_request(fgd->bulk_ep, fgd->bulk_req);
+	usb_ep_free_request(cdev->gadget->ep0, fgd->ctrl_req);
+	fgd->ctrl_req = NULL;
+	fgd->bulk_req = NULL;
+	fgd->bulk_ep = NULL;
+
+	usb_free_all_descriptors(f);
+}
+
+static int f_gud_drm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_gud_drm_opts *opts = fi_to_f_gud_drm_opts(f->fi);
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+	struct usb_request *ctrl_req, *bulk_req;
+	struct gud_drm_gadget *gdg;
+	struct usb_string *us;
+	void *buf;
+	int ret;
+
+	us = usb_gstrings_attach(cdev, f_gud_drm_strings,
+				 ARRAY_SIZE(f_gud_drm_string_defs));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+
+	f_gud_drm_intf.iInterface = us[0].id;
+
+	ret = usb_interface_id(c, f);
+	if (ret < 0)
+		return ret;
+
+	fgd->interface_id = ret;
+	f_gud_drm_intf.bInterfaceNumber = fgd->interface_id;
+
+	fgd->bulk_ep = usb_ep_autoconfig(cdev->gadget, &f_gud_drm_fs_out_desc);
+	if (!fgd->bulk_ep)
+		return -ENODEV;
+
+	f_gud_drm_hs_out_desc.bEndpointAddress = f_gud_drm_fs_out_desc.bEndpointAddress;
+
+	f_gud_drm_ss_out_desc.bEndpointAddress = f_gud_drm_fs_out_desc.bEndpointAddress;
+
+	ret = usb_assign_descriptors(f, f_gud_drm_fs_function, f_gud_drm_hs_function,
+				     f_gud_drm_ss_function, NULL);
+	if (ret)
+		return ret;
+
+	ctrl_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
+	if (!ctrl_req) {
+		ret = -ENOMEM;
+		goto fail_free_descs;
+	}
+
+	ctrl_req->buf = fgd->ctrl_req_buf;
+	ctrl_req->complete = f_gud_drm_ctrl_req_complete;
+	ctrl_req->context = fgd;
+
+	gdg = gud_drm_gadget_init(opts->drm_dev, opts->backlight_dev, &fgd->max_buffer_size);
+	if (IS_ERR(gdg)) {
+		ret = PTR_ERR(gdg);
+		goto fail_free_ctrl_req;
+	}
+
+	bulk_req = usb_ep_alloc_request(fgd->bulk_ep, GFP_KERNEL);
+	if (!bulk_req) {
+		ret = -ENOMEM;
+		goto fail_free_ctrl_req;
+	}
+
+	buf = kmalloc(fgd->max_buffer_size, GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto fail_free_bulk_req;
+	}
+
+	bulk_req->complete = f_gud_drm_bulk_complete;
+	bulk_req->context = fgd;
+	bulk_req->buf = buf;
+
+	fgd->ctrl_req = ctrl_req;
+	fgd->bulk_req = bulk_req;
+	fgd->gdg = gdg;
+
+	return 0;
+
+fail_free_bulk_req:
+	usb_ep_free_request(fgd->bulk_ep, bulk_req);
+fail_free_ctrl_req:
+	usb_ep_free_request(cdev->gadget->ep0, ctrl_req);
+fail_free_descs:
+	usb_free_all_descriptors(f);
+
+	return ret;
+}
+
+static void f_gud_drm_free_func(struct usb_function *f)
+{
+	struct f_gud_drm_opts *opts = container_of(f->fi, struct f_gud_drm_opts, func_inst);
+	struct f_gud_drm *fgd = func_to_f_gud_drm(f);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+
+	kfree(fgd->ctrl_req_buf);
+	kfree(fgd);
+}
+
+static struct usb_function *f_gud_drm_alloc_func(struct usb_function_instance *fi)
+{
+	struct f_gud_drm_opts *opts = fi_to_f_gud_drm_opts(fi);
+	struct usb_function *func;
+	struct f_gud_drm *fgd;
+
+	fgd = kzalloc(sizeof(*fgd), GFP_KERNEL);
+	if (!fgd)
+		return ERR_PTR(-ENOMEM);
+
+	fgd->ctrl_req_buf = kmalloc(GUD_DRM_MAX_TRANSFER_SIZE, GFP_KERNEL);
+	if (!fgd->ctrl_req_buf)
+		goto error;
+
+	spin_lock_init(&fgd->lock);
+	INIT_WORK(&fgd->worker, f_gud_drm_worker);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt++;
+	mutex_unlock(&opts->lock);
+
+	func = &fgd->func;
+	func->name = "gud_drm";
+	func->bind = f_gud_drm_bind;
+	func->unbind = f_gud_drm_unbind;
+	func->set_alt = f_gud_drm_set_alt;
+	func->req_match = f_gud_drm_req_match;
+	func->setup = f_gud_drm_setup;
+	func->disable = f_gud_drm_disable;
+	func->free_func = f_gud_drm_free_func;
+
+	return func;
+
+error:
+	kfree(fgd);
+
+	return ERR_PTR(-ENOMEM);
+}
+
+static ssize_t f_gud_drm_opts_drm_dev_show(struct config_item *item, char *page)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->drm_dev);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_gud_drm_opts_drm_dev_store(struct config_item *item,
+					    const char *page, size_t len)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	unsigned int num;
+	int ret;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	ret = kstrtouint(page, 0, &num);
+	if (ret)
+		goto unlock;
+
+	opts->drm_dev = num;
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_drm_opts_, drm_dev);
+
+static ssize_t f_gud_drm_opts_backlight_dev_show(struct config_item *item, char *page)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	ssize_t ret = 0;
+
+	mutex_lock(&opts->lock);
+	if (opts->backlight_dev)
+		ret = strscpy(page, opts->backlight_dev, PAGE_SIZE);
+	else
+		page[0] = '\0';
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+static ssize_t f_gud_drm_opts_backlight_dev_store(struct config_item *item,
+						  const char *page, size_t len)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+	ssize_t ret;
+	char *name;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	name = kstrndup(page, len, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	kfree(opts->backlight_dev);
+	opts->backlight_dev = name;
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_drm_opts_, backlight_dev);
+
+static struct configfs_attribute *f_gud_drm_attrs[] = {
+	&f_gud_drm_opts_attr_drm_dev,
+	&f_gud_drm_opts_attr_backlight_dev,
+	NULL,
+};
+
+static void f_gud_drm_attr_release(struct config_item *item)
+{
+	struct f_gud_drm_opts *opts = ci_to_f_gud_drm_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_gud_drm_item_ops = {
+	.release	= f_gud_drm_attr_release,
+};
+
+static const struct config_item_type f_gud_drm_func_type = {
+	.ct_item_ops	= &f_gud_drm_item_ops,
+	.ct_attrs	= f_gud_drm_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_gud_drm_free_func_inst(struct usb_function_instance *fi)
+{
+	struct f_gud_drm_opts *opts = fi_to_f_gud_drm_opts(fi);
+
+	mutex_destroy(&opts->lock);
+	kfree(opts->backlight_dev);
+	kfree(opts);
+}
+
+static struct usb_function_instance *f_gud_drm_alloc_func_inst(void)
+{
+	struct f_gud_drm_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_gud_drm_free_func_inst;
+
+	config_group_init_type_name(&opts->func_inst.group, "", &f_gud_drm_func_type);
+
+	return &opts->func_inst;
+}
+
+DECLARE_USB_FUNCTION_INIT(gud_drm, f_gud_drm_alloc_func_inst, f_gud_drm_alloc_func);
+
+MODULE_DESCRIPTION("Generic USB Display Gadget");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 02/10] drm: Add backlight helper
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-04-29 14:13     ` Hans de Goede
  -1 siblings, 0 replies; 66+ messages in thread
From: Hans de Goede @ 2020-04-29 14:13 UTC (permalink / raw)
  To: Noralf Trønnes, dri-devel, linux-usb
  Cc: Jani Nikula, Martin Peres, Daniel Thompson, Christian Kellner

Hi Noralf,

On 4/29/20 2:48 PM, Noralf Trønnes wrote:
> This adds a function that creates a backlight device for a connector.
> It does not deal with the KMS backlight ABI proposition[1] to add a
> connector property. It only takes the current best practise to standardise
> the creation of a backlight device for DRM drivers while we wait for the
> property.
> 
> The brightness value is set using a connector state variable and an atomic
> commit.
> 
> I have looked through some of the backlight users and this is what I've found:
> 
> GNOME [2]
> ---------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual

I'm afraid that this is an incaccurate view of how GNOME handles the
brightness. gnome-settings-daemon (g-s-d) exports a DBUS property which has
a range of 0 - 100%.  But it also offers step-up and step-down DBUS methods
which are used for handling brightness hotkey presses.

This is important because g-s-d internally also keeps a step_size variable
which depends on the brightness_max value of the sysfs backlight interface,
like this:

BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)

This is important because some older laptops where we depend on the
vendor specific ACPI method (from e.g. dell-laptop or thinkpad_acpi)
there are only 8 levels. So if g-s-d where to simply fake a 1-100
range and would leave the stepping up to the DBus API user and that
user would want 20 steps, so 5 % per step, then the user would get

Start      -> 100% -> level 8
Press down ->  95% -> level 7
Press down ->  90% -> level 7 *no change*
etc.

Somewhat related on some embedded ARM devices there are tricks where
when the entire scene being rendered does not use 100% white as color,
the entire scene has all its rgb values upscaled (too a curve) so that
the brightest colors do hit 100% of one of r/g/b, combined with dimming
the backlight a bit to save power. As you can imagine for tricks like
these you want as much backlight control precision as possible.

So any backlight infra we add must expose the true range of the
backlight control and not normalize it to a 0-100 range.

So sorry, but nack for the current version because of the hardcoding
of the range.

Also the scale really should be specified by the driver, or be hardcoded
to BACKLIGHT_SCALE_UNKNOWN for now. In many cases we do not really know.
But for e.g. the acpi_video firmware backlight interface a good guess is
that it actually represents a perceptual scale rather then controlling
the wattage.

Where as the native i915 backlight interface really is controlling
the wattage without any perceptual correction.

Another problem with your proposal is that it seems to assume that
the backlight is controlled by the drm/kms driver. On x86 we have
atleast 3 different drivers for the backlight:

1) The i915 (or amd/nouveau) native driver which more or less
directly pokes the PWM controller of the GPU.
2) The ACPI video standard backlight interface
3) Vendor specific ACPI interfaces from older laptops

ATM we always register 1. which could remain unchanged with
your code and then also register 2/3 if we (the kernel) think
that will work better (*) and then rely on userspace prefering
these (they have a different backlight_type) over 1.

Ideally any infra we add will also offer the option to tie
2. or 3. to the connector...

Regards,

Hans



*) e.g. it will work while the others will not work at all




> 
> Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
> Can connect connector and backlight using the sysfs device.
> 
> KDE [3]
> -------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual
> 
> Weston [4]
> ----------
> 
> Brightness range: 0-255
> Scale: Assumes perceptual
> 
> Chromium OS [5]
> ---------------
> 
> Brightness range: 0-100
> Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)
> 
> xserver [6]
> -----------
> 
> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
> Scale: Assumes perceptual
> 
> The builtin modesetting driver[7] does not support Backlight, Intel[8] does.
> 
> [1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
> [2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
> [3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
> [4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
> [5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
> [6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
> [7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
> [8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
> 
> Cc: Hans de Goede <hdegoede@redhat.com>
> Cc: Jani Nikula <jani.nikula@linux.intel.com>
> Cc: Martin Peres <martin.peres@linux.intel.com>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>   Documentation/gpu/drm-kms-helpers.rst  |   6 +
>   drivers/gpu/drm/Kconfig                |   7 ++
>   drivers/gpu/drm/Makefile               |   1 +
>   drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>   include/drm/drm_backlight_helper.h     |   9 ++
>   include/drm/drm_connector.h            |  10 ++
>   6 files changed, 187 insertions(+)
>   create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>   create mode 100644 include/drm/drm_backlight_helper.h
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 9668a7fe2408..29a2f4b49fd2 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>   
>   .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>      :export:
> +
> +Backlight Helper Reference
> +==========================
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
> +   :export:
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index d0aa6cff2e02..f6e13e18c9ca 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>   	help
>   	  Choose this if you need the GEM shmem helper functions
>   
> +config DRM_BACKLIGHT_HELPER
> +	bool
> +	depends on DRM
> +	select BACKLIGHT_CLASS_DEVICE
> +	help
> +	  Choose this if you need the backlight device helper functions
> +
>   config DRM_VM
>   	bool
>   	depends on DRM && MMU
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 6493088a0fdd..0d17662dde0a 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
>   drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>   drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>   drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>   
>   obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>   obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
> diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
> new file mode 100644
> index 000000000000..06e6a75d1d0a
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_backlight_helper.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +/*
> + * Copyright 2020 Noralf Trønnes
> + */
> +
> +#include <linux/backlight.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_file.h>
> +
> +static int drm_backlight_update_status(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_connector_state *connector_state;
> +	struct drm_device *dev = connector->dev;
> +	struct drm_modeset_acquire_ctx ctx;
> +	struct drm_atomic_state *state;
> +	int ret;
> +
> +	state = drm_atomic_state_alloc(dev);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	drm_modeset_acquire_init(&ctx, 0);
> +	state->acquire_ctx = &ctx;
> +retry:
> +	connector_state = drm_atomic_get_connector_state(state, connector);
> +	if (IS_ERR(connector_state)) {
> +		ret = PTR_ERR(connector_state);
> +		goto out;
> +	}
> +
> +	connector_state->backlight_brightness = bd->props.brightness;
> +
> +	ret = drm_atomic_commit(state);
> +out:
> +	if (ret == -EDEADLK) {
> +		drm_atomic_state_clear(state);
> +		drm_modeset_backoff(&ctx);
> +		goto retry;
> +	}
> +
> +	drm_atomic_state_put(state);
> +
> +	drm_modeset_drop_locks(&ctx);
> +	drm_modeset_acquire_fini(&ctx);
> +
> +	return ret;
> +}
> +
> +static int drm_backlight_get_brightness(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_device *dev = connector->dev;
> +	int brightness;
> +
> +	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
> +	brightness = connector->state->backlight_brightness;
> +	drm_modeset_unlock(&dev->mode_config.connection_mutex);
> +
> +	return brightness;
> +}
> +
> +static const struct backlight_ops drm_backlight_ops = {
> +	.get_brightness = drm_backlight_get_brightness,
> +	.update_status	= drm_backlight_update_status,
> +};
> +
> +/* Can be exported for drivers carrying a legacy name */
> +static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
> +{
> +	struct backlight_device *bd;
> +	const struct backlight_properties props = {
> +		.type = BACKLIGHT_RAW,
> +		.scale = BACKLIGHT_SCALE_NON_LINEAR,
> +		.max_brightness = 100,
> +	};
> +
> +	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
> +		    !drm_drv_uses_atomic_modeset(connector->dev) ||
> +		    !connector->kdev))
> +		return -EINVAL;
> +
> +	bd = backlight_device_register(name, connector->kdev, connector,
> +				       &drm_backlight_ops, &props);
> +	if (IS_ERR(bd))
> +		return PTR_ERR(bd);
> +
> +	connector->backlight = bd;
> +
> +	return 0;
> +}
> +
> +/**
> + * drm_backlight_register() - Register a backlight device for a connector
> + * @connector: Connector
> + *
> + * This function registers a backlight device for @connector with the following
> + * characteristics:
> + *
> + * - The connector sysfs device is used as a parent device for the backlight device.
> + *   Userspace can use this to connect backlight device and connector.
> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
> + * - Type is "raw"
> + * - Scale is "non-linear" (perceptual)
> + * - Max brightness is 100 giving a range of 0-100 inclusive
> + * - Reading sysfs **brightness** returns the backlight device property
> + * - Reading sysfs **actual_brightness** returns the connector state value
> + * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
> + *   be used to control power.
> + * - Backlight device suspend/resume events are ignored.
> + *
> + * Note:
> + *
> + * Brightness zero should not turn off backlight it should be the minimum
> + * brightness, DPMS handles power.
> + *
> + * This function must be called from &drm_connector_funcs->late_register() since
> + * it depends on the sysfs device.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_backlight_register(struct drm_connector *connector)
> +{
> +	const char *name = NULL;
> +	int ret;
> +
> +	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
> +			 connector->dev->primary->index, connector->name);
> +	if (!name)
> +		return -ENOMEM;
> +
> +	ret = drm_backlight_register_with_name(connector, name);
> +	kfree(name);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_backlight_register);
> +
> +/**
> + * drm_backlight_unregister() - Unregister backlight device
> + * @connector: Connector
> + *
> + * Unregister a backlight device. This must be called from the
> + * &drm_connector_funcs->early_unregister() callback.
> + */
> +void drm_backlight_unregister(struct drm_connector *connector)
> +{
> +	backlight_device_unregister(connector->backlight);
> +}
> +EXPORT_SYMBOL(drm_backlight_unregister);
> diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
> new file mode 100644
> index 000000000000..4151b66eb0b4
> --- /dev/null
> +++ b/include/drm/drm_backlight_helper.h
> @@ -0,0 +1,9 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> +
> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
> +
> +int drm_backlight_register(struct drm_connector *connector);
> +void drm_backlight_unregister(struct drm_connector *connector);
> +
> +#endif
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 221910948b37..ce678b694f45 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -32,6 +32,7 @@
>   
>   #include <uapi/drm/drm_mode.h>
>   
> +struct backlight_device;
>   struct drm_connector_helper_funcs;
>   struct drm_modeset_acquire_ctx;
>   struct drm_device;
> @@ -656,6 +657,12 @@ struct drm_connector_state {
>   	 */
>   	u8 max_bpc;
>   
> +	/**
> +	 * @backlight_brightness: Brightness value of the connector backlight
> +	 * device. See drm_backlight_register().
> +	 */
> +	u8 backlight_brightness;
> +
>   	/**
>   	 * @hdr_output_metadata:
>   	 * DRM blob property for HDR output metadata
> @@ -1422,6 +1429,9 @@ struct drm_connector {
>   
>   	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
>   	struct hdr_sink_metadata hdr_sink_metadata;
> +
> +	/** @backlight: Backlight device. See drm_backlight_register() */
> +	struct backlight_device *backlight;
>   };
>   
>   #define obj_to_connector(x) container_of(x, struct drm_connector, base)
> 


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

* Re: [PATCH 02/10] drm: Add backlight helper
@ 2020-04-29 14:13     ` Hans de Goede
  0 siblings, 0 replies; 66+ messages in thread
From: Hans de Goede @ 2020-04-29 14:13 UTC (permalink / raw)
  To: Noralf Trønnes, dri-devel, linux-usb
  Cc: Daniel Thompson, Christian Kellner

Hi Noralf,

On 4/29/20 2:48 PM, Noralf Trønnes wrote:
> This adds a function that creates a backlight device for a connector.
> It does not deal with the KMS backlight ABI proposition[1] to add a
> connector property. It only takes the current best practise to standardise
> the creation of a backlight device for DRM drivers while we wait for the
> property.
> 
> The brightness value is set using a connector state variable and an atomic
> commit.
> 
> I have looked through some of the backlight users and this is what I've found:
> 
> GNOME [2]
> ---------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual

I'm afraid that this is an incaccurate view of how GNOME handles the
brightness. gnome-settings-daemon (g-s-d) exports a DBUS property which has
a range of 0 - 100%.  But it also offers step-up and step-down DBUS methods
which are used for handling brightness hotkey presses.

This is important because g-s-d internally also keeps a step_size variable
which depends on the brightness_max value of the sysfs backlight interface,
like this:

BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)

This is important because some older laptops where we depend on the
vendor specific ACPI method (from e.g. dell-laptop or thinkpad_acpi)
there are only 8 levels. So if g-s-d where to simply fake a 1-100
range and would leave the stepping up to the DBus API user and that
user would want 20 steps, so 5 % per step, then the user would get

Start      -> 100% -> level 8
Press down ->  95% -> level 7
Press down ->  90% -> level 7 *no change*
etc.

Somewhat related on some embedded ARM devices there are tricks where
when the entire scene being rendered does not use 100% white as color,
the entire scene has all its rgb values upscaled (too a curve) so that
the brightest colors do hit 100% of one of r/g/b, combined with dimming
the backlight a bit to save power. As you can imagine for tricks like
these you want as much backlight control precision as possible.

So any backlight infra we add must expose the true range of the
backlight control and not normalize it to a 0-100 range.

So sorry, but nack for the current version because of the hardcoding
of the range.

Also the scale really should be specified by the driver, or be hardcoded
to BACKLIGHT_SCALE_UNKNOWN for now. In many cases we do not really know.
But for e.g. the acpi_video firmware backlight interface a good guess is
that it actually represents a perceptual scale rather then controlling
the wattage.

Where as the native i915 backlight interface really is controlling
the wattage without any perceptual correction.

Another problem with your proposal is that it seems to assume that
the backlight is controlled by the drm/kms driver. On x86 we have
atleast 3 different drivers for the backlight:

1) The i915 (or amd/nouveau) native driver which more or less
directly pokes the PWM controller of the GPU.
2) The ACPI video standard backlight interface
3) Vendor specific ACPI interfaces from older laptops

ATM we always register 1. which could remain unchanged with
your code and then also register 2/3 if we (the kernel) think
that will work better (*) and then rely on userspace prefering
these (they have a different backlight_type) over 1.

Ideally any infra we add will also offer the option to tie
2. or 3. to the connector...

Regards,

Hans



*) e.g. it will work while the others will not work at all




> 
> Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
> Can connect connector and backlight using the sysfs device.
> 
> KDE [3]
> -------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual
> 
> Weston [4]
> ----------
> 
> Brightness range: 0-255
> Scale: Assumes perceptual
> 
> Chromium OS [5]
> ---------------
> 
> Brightness range: 0-100
> Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)
> 
> xserver [6]
> -----------
> 
> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
> Scale: Assumes perceptual
> 
> The builtin modesetting driver[7] does not support Backlight, Intel[8] does.
> 
> [1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
> [2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
> [3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
> [4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
> [5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
> [6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
> [7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
> [8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
> 
> Cc: Hans de Goede <hdegoede@redhat.com>
> Cc: Jani Nikula <jani.nikula@linux.intel.com>
> Cc: Martin Peres <martin.peres@linux.intel.com>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>   Documentation/gpu/drm-kms-helpers.rst  |   6 +
>   drivers/gpu/drm/Kconfig                |   7 ++
>   drivers/gpu/drm/Makefile               |   1 +
>   drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>   include/drm/drm_backlight_helper.h     |   9 ++
>   include/drm/drm_connector.h            |  10 ++
>   6 files changed, 187 insertions(+)
>   create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>   create mode 100644 include/drm/drm_backlight_helper.h
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 9668a7fe2408..29a2f4b49fd2 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>   
>   .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>      :export:
> +
> +Backlight Helper Reference
> +==========================
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
> +   :export:
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index d0aa6cff2e02..f6e13e18c9ca 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>   	help
>   	  Choose this if you need the GEM shmem helper functions
>   
> +config DRM_BACKLIGHT_HELPER
> +	bool
> +	depends on DRM
> +	select BACKLIGHT_CLASS_DEVICE
> +	help
> +	  Choose this if you need the backlight device helper functions
> +
>   config DRM_VM
>   	bool
>   	depends on DRM && MMU
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 6493088a0fdd..0d17662dde0a 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
>   drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>   drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>   drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>   
>   obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>   obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
> diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
> new file mode 100644
> index 000000000000..06e6a75d1d0a
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_backlight_helper.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +/*
> + * Copyright 2020 Noralf Trønnes
> + */
> +
> +#include <linux/backlight.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_file.h>
> +
> +static int drm_backlight_update_status(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_connector_state *connector_state;
> +	struct drm_device *dev = connector->dev;
> +	struct drm_modeset_acquire_ctx ctx;
> +	struct drm_atomic_state *state;
> +	int ret;
> +
> +	state = drm_atomic_state_alloc(dev);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	drm_modeset_acquire_init(&ctx, 0);
> +	state->acquire_ctx = &ctx;
> +retry:
> +	connector_state = drm_atomic_get_connector_state(state, connector);
> +	if (IS_ERR(connector_state)) {
> +		ret = PTR_ERR(connector_state);
> +		goto out;
> +	}
> +
> +	connector_state->backlight_brightness = bd->props.brightness;
> +
> +	ret = drm_atomic_commit(state);
> +out:
> +	if (ret == -EDEADLK) {
> +		drm_atomic_state_clear(state);
> +		drm_modeset_backoff(&ctx);
> +		goto retry;
> +	}
> +
> +	drm_atomic_state_put(state);
> +
> +	drm_modeset_drop_locks(&ctx);
> +	drm_modeset_acquire_fini(&ctx);
> +
> +	return ret;
> +}
> +
> +static int drm_backlight_get_brightness(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_device *dev = connector->dev;
> +	int brightness;
> +
> +	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
> +	brightness = connector->state->backlight_brightness;
> +	drm_modeset_unlock(&dev->mode_config.connection_mutex);
> +
> +	return brightness;
> +}
> +
> +static const struct backlight_ops drm_backlight_ops = {
> +	.get_brightness = drm_backlight_get_brightness,
> +	.update_status	= drm_backlight_update_status,
> +};
> +
> +/* Can be exported for drivers carrying a legacy name */
> +static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
> +{
> +	struct backlight_device *bd;
> +	const struct backlight_properties props = {
> +		.type = BACKLIGHT_RAW,
> +		.scale = BACKLIGHT_SCALE_NON_LINEAR,
> +		.max_brightness = 100,
> +	};
> +
> +	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
> +		    !drm_drv_uses_atomic_modeset(connector->dev) ||
> +		    !connector->kdev))
> +		return -EINVAL;
> +
> +	bd = backlight_device_register(name, connector->kdev, connector,
> +				       &drm_backlight_ops, &props);
> +	if (IS_ERR(bd))
> +		return PTR_ERR(bd);
> +
> +	connector->backlight = bd;
> +
> +	return 0;
> +}
> +
> +/**
> + * drm_backlight_register() - Register a backlight device for a connector
> + * @connector: Connector
> + *
> + * This function registers a backlight device for @connector with the following
> + * characteristics:
> + *
> + * - The connector sysfs device is used as a parent device for the backlight device.
> + *   Userspace can use this to connect backlight device and connector.
> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
> + * - Type is "raw"
> + * - Scale is "non-linear" (perceptual)
> + * - Max brightness is 100 giving a range of 0-100 inclusive
> + * - Reading sysfs **brightness** returns the backlight device property
> + * - Reading sysfs **actual_brightness** returns the connector state value
> + * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
> + *   be used to control power.
> + * - Backlight device suspend/resume events are ignored.
> + *
> + * Note:
> + *
> + * Brightness zero should not turn off backlight it should be the minimum
> + * brightness, DPMS handles power.
> + *
> + * This function must be called from &drm_connector_funcs->late_register() since
> + * it depends on the sysfs device.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_backlight_register(struct drm_connector *connector)
> +{
> +	const char *name = NULL;
> +	int ret;
> +
> +	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
> +			 connector->dev->primary->index, connector->name);
> +	if (!name)
> +		return -ENOMEM;
> +
> +	ret = drm_backlight_register_with_name(connector, name);
> +	kfree(name);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_backlight_register);
> +
> +/**
> + * drm_backlight_unregister() - Unregister backlight device
> + * @connector: Connector
> + *
> + * Unregister a backlight device. This must be called from the
> + * &drm_connector_funcs->early_unregister() callback.
> + */
> +void drm_backlight_unregister(struct drm_connector *connector)
> +{
> +	backlight_device_unregister(connector->backlight);
> +}
> +EXPORT_SYMBOL(drm_backlight_unregister);
> diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
> new file mode 100644
> index 000000000000..4151b66eb0b4
> --- /dev/null
> +++ b/include/drm/drm_backlight_helper.h
> @@ -0,0 +1,9 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> +
> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
> +
> +int drm_backlight_register(struct drm_connector *connector);
> +void drm_backlight_unregister(struct drm_connector *connector);
> +
> +#endif
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 221910948b37..ce678b694f45 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -32,6 +32,7 @@
>   
>   #include <uapi/drm/drm_mode.h>
>   
> +struct backlight_device;
>   struct drm_connector_helper_funcs;
>   struct drm_modeset_acquire_ctx;
>   struct drm_device;
> @@ -656,6 +657,12 @@ struct drm_connector_state {
>   	 */
>   	u8 max_bpc;
>   
> +	/**
> +	 * @backlight_brightness: Brightness value of the connector backlight
> +	 * device. See drm_backlight_register().
> +	 */
> +	u8 backlight_brightness;
> +
>   	/**
>   	 * @hdr_output_metadata:
>   	 * DRM blob property for HDR output metadata
> @@ -1422,6 +1429,9 @@ struct drm_connector {
>   
>   	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
>   	struct hdr_sink_metadata hdr_sink_metadata;
> +
> +	/** @backlight: Backlight device. See drm_backlight_register() */
> +	struct backlight_device *backlight;
>   };
>   
>   #define obj_to_connector(x) container_of(x, struct drm_connector, base)
> 

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 02/10] drm: Add backlight helper
  2020-04-29 14:13     ` Hans de Goede
@ 2020-04-29 18:40       ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 18:40 UTC (permalink / raw)
  To: Hans de Goede, dri-devel, linux-usb
  Cc: Jani Nikula, Martin Peres, Daniel Thompson, Christian Kellner



Den 29.04.2020 16.13, skrev Hans de Goede:
> Hi Noralf,
> 
> On 4/29/20 2:48 PM, Noralf Trønnes wrote:
>> This adds a function that creates a backlight device for a connector.
>> It does not deal with the KMS backlight ABI proposition[1] to add a
>> connector property. It only takes the current best practise to
>> standardise
>> the creation of a backlight device for DRM drivers while we wait for the
>> property.
>>
>> The brightness value is set using a connector state variable and an
>> atomic
>> commit.
>>
>> I have looked through some of the backlight users and this is what
>> I've found:
>>
>> GNOME [2]
>> ---------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
> 
> I'm afraid that this is an incaccurate view of how GNOME handles the
> brightness. gnome-settings-daemon (g-s-d) exports a DBUS property which has
> a range of 0 - 100%.  But it also offers step-up and step-down DBUS methods
> which are used for handling brightness hotkey presses.
> 
> This is important because g-s-d internally also keeps a step_size variable
> which depends on the brightness_max value of the sysfs backlight interface,
> like this:
> 
> BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)
> 
> This is important because some older laptops where we depend on the
> vendor specific ACPI method (from e.g. dell-laptop or thinkpad_acpi)
> there are only 8 levels. So if g-s-d where to simply fake a 1-100
> range and would leave the stepping up to the DBus API user and that
> user would want 20 steps, so 5 % per step, then the user would get
> 
> Start      -> 100% -> level 8
> Press down ->  95% -> level 7
> Press down ->  90% -> level 7 *no change*
> etc.
> 
> Somewhat related on some embedded ARM devices there are tricks where
> when the entire scene being rendered does not use 100% white as color,
> the entire scene has all its rgb values upscaled (too a curve) so that
> the brightest colors do hit 100% of one of r/g/b, combined with dimming
> the backlight a bit to save power. As you can imagine for tricks like
> these you want as much backlight control precision as possible.
> 
> So any backlight infra we add must expose the true range of the
> backlight control and not normalize it to a 0-100 range.
> 
> So sorry, but nack for the current version because of the hardcoding
> of the range.

No problem, I just had to start from somewhere, and I started with: Give
the driver developer as few options as possible, no more than necessary,
but I didn't really know what was necessary :-)

The reason I chose a 0-100 range is because the backlight property ABI
proposal had this range and it maps so nicely to percent. And can the
ordinary human see brightness changes in more than 100 steps?

This helper is only to be used by drm drivers and I assumed that all the
current drivers registering a backlight device could at least do that range.

Looking through the drivers and their max_brightness values that
assumption isn't quite right:

amd: 255
gma500: 100
i915: <don't know, register read>
nouveau/nv40: 31
nouveau/nv50: 100
radeon: 255
shmobile: <don't know, from platform data>

panel-dsi-cm.c: 255
panel-jdi-lt070me05000.c: 255
panel-orisetech-otm8009a.c: 255
panel-raydium-rm67191.c: 255
panel-samsung-s6e63m0.c: 10
panel-sony-acx424akp.c: 1023
panel-samsung-s6e3ha2.c: 100
panel-samsung-s6e63j0x03.c: 100
panel-sony-acx565akm.c: 255
bridge/parade-ps8622.c: 255

I'll add max_brightness as an argument together with scale which you
commented on below.

> 
> Also the scale really should be specified by the driver, or be hardcoded
> to BACKLIGHT_SCALE_UNKNOWN for now. In many cases we do not really know.
> But for e.g. the acpi_video firmware backlight interface a good guess is
> that it actually represents a perceptual scale rather then controlling
> the wattage.
> 
> Where as the native i915 backlight interface really is controlling
> the wattage without any perceptual correction.
> 
> Another problem with your proposal is that it seems to assume that
> the backlight is controlled by the drm/kms driver. On x86 we have

Yes, this backlight device is just for drm drivers.
The reason I spend time on this is because I want to pass backlight
brightness changes through the atomic machinery like any other state change.

Noralf.

> atleast 3 different drivers for the backlight:
> 
> 1) The i915 (or amd/nouveau) native driver which more or less
> directly pokes the PWM controller of the GPU.
> 2) The ACPI video standard backlight interface
> 3) Vendor specific ACPI interfaces from older laptops
> 
> ATM we always register 1. which could remain unchanged with
> your code and then also register 2/3 if we (the kernel) think
> that will work better (*) and then rely on userspace prefering
> these (they have a different backlight_type) over 1.
> 
> Ideally any infra we add will also offer the option to tie
> 2. or 3. to the connector...
> 
> Regards,
> 
> Hans
> 
> 
> 
> *) e.g. it will work while the others will not work at all
> 
> 
> 
> 
>>
>> Avoids setting the sysfs brightness value to zero if max_brightness >=
>> 99.
>> Can connect connector and backlight using the sysfs device.
>>
>> KDE [3]
>> -------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
>>
>> Weston [4]
>> ----------
>>
>> Brightness range: 0-255
>> Scale: Assumes perceptual
>>
>> Chromium OS [5]
>> ---------------
>>
>> Brightness range: 0-100
>> Scale: Depends on the sysfs file 'scale' which is a recent addition
>> (2019)
>>
>> xserver [6]
>> -----------
>>
>> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
>> Scale: Assumes perceptual
>>
>> The builtin modesetting driver[7] does not support Backlight, Intel[8]
>> does.
>>
>> [1]
>> https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
>>
>> [2]
>> https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
>>
>> [3]
>> https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
>>
>> [4]
>> https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
>>
>> [5]
>> https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
>>
>> [6]
>> https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
>> [7]
>> https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
>>
>> [8]
>> https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
>>
>>
>> Cc: Hans de Goede <hdegoede@redhat.com>
>> Cc: Jani Nikula <jani.nikula@linux.intel.com>
>> Cc: Martin Peres <martin.peres@linux.intel.com>
>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>   Documentation/gpu/drm-kms-helpers.rst  |   6 +
>>   drivers/gpu/drm/Kconfig                |   7 ++
>>   drivers/gpu/drm/Makefile               |   1 +
>>   drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>>   include/drm/drm_backlight_helper.h     |   9 ++
>>   include/drm/drm_connector.h            |  10 ++
>>   6 files changed, 187 insertions(+)
>>   create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>>   create mode 100644 include/drm/drm_backlight_helper.h
>>
>> diff --git a/Documentation/gpu/drm-kms-helpers.rst
>> b/Documentation/gpu/drm-kms-helpers.rst
>> index 9668a7fe2408..29a2f4b49fd2 100644
>> --- a/Documentation/gpu/drm-kms-helpers.rst
>> +++ b/Documentation/gpu/drm-kms-helpers.rst
>> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>>     .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>>      :export:
>> +
>> +Backlight Helper Reference
>> +==========================
>> +
>> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
>> +   :export:
>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>> index d0aa6cff2e02..f6e13e18c9ca 100644
>> --- a/drivers/gpu/drm/Kconfig
>> +++ b/drivers/gpu/drm/Kconfig
>> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>>       help
>>         Choose this if you need the GEM shmem helper functions
>>   +config DRM_BACKLIGHT_HELPER
>> +    bool
>> +    depends on DRM
>> +    select BACKLIGHT_CLASS_DEVICE
>> +    help
>> +      Choose this if you need the backlight device helper functions
>> +
>>   config DRM_VM
>>       bool
>>       depends on DRM && MMU
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index 6493088a0fdd..0d17662dde0a 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) +=
>> drm_fb_helper.o
>>   drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>>   drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>>   drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
>> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>>     obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>>   obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
>> diff --git a/drivers/gpu/drm/drm_backlight_helper.c
>> b/drivers/gpu/drm/drm_backlight_helper.c
>> new file mode 100644
>> index 000000000000..06e6a75d1d0a
>> --- /dev/null
>> +++ b/drivers/gpu/drm/drm_backlight_helper.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0 OR MIT
>> +/*
>> + * Copyright 2020 Noralf Trønnes
>> + */
>> +
>> +#include <linux/backlight.h>
>> +
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_file.h>
>> +
>> +static int drm_backlight_update_status(struct backlight_device *bd)
>> +{
>> +    struct drm_connector *connector = bl_get_data(bd);
>> +    struct drm_connector_state *connector_state;
>> +    struct drm_device *dev = connector->dev;
>> +    struct drm_modeset_acquire_ctx ctx;
>> +    struct drm_atomic_state *state;
>> +    int ret;
>> +
>> +    state = drm_atomic_state_alloc(dev);
>> +    if (!state)
>> +        return -ENOMEM;
>> +
>> +    drm_modeset_acquire_init(&ctx, 0);
>> +    state->acquire_ctx = &ctx;
>> +retry:
>> +    connector_state = drm_atomic_get_connector_state(state, connector);
>> +    if (IS_ERR(connector_state)) {
>> +        ret = PTR_ERR(connector_state);
>> +        goto out;
>> +    }
>> +
>> +    connector_state->backlight_brightness = bd->props.brightness;
>> +
>> +    ret = drm_atomic_commit(state);
>> +out:
>> +    if (ret == -EDEADLK) {
>> +        drm_atomic_state_clear(state);
>> +        drm_modeset_backoff(&ctx);
>> +        goto retry;
>> +    }
>> +
>> +    drm_atomic_state_put(state);
>> +
>> +    drm_modeset_drop_locks(&ctx);
>> +    drm_modeset_acquire_fini(&ctx);
>> +
>> +    return ret;
>> +}
>> +
>> +static int drm_backlight_get_brightness(struct backlight_device *bd)
>> +{
>> +    struct drm_connector *connector = bl_get_data(bd);
>> +    struct drm_device *dev = connector->dev;
>> +    int brightness;
>> +
>> +    drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
>> +    brightness = connector->state->backlight_brightness;
>> +    drm_modeset_unlock(&dev->mode_config.connection_mutex);
>> +
>> +    return brightness;
>> +}
>> +
>> +static const struct backlight_ops drm_backlight_ops = {
>> +    .get_brightness = drm_backlight_get_brightness,
>> +    .update_status    = drm_backlight_update_status,
>> +};
>> +
>> +/* Can be exported for drivers carrying a legacy name */
>> +static int drm_backlight_register_with_name(struct drm_connector
>> *connector, const char *name)
>> +{
>> +    struct backlight_device *bd;
>> +    const struct backlight_properties props = {
>> +        .type = BACKLIGHT_RAW,
>> +        .scale = BACKLIGHT_SCALE_NON_LINEAR,
>> +        .max_brightness = 100,
>> +    };
>> +
>> +    if (WARN_ON(!drm_core_check_feature(connector->dev,
>> DRIVER_MODESET) ||
>> +            !drm_drv_uses_atomic_modeset(connector->dev) ||
>> +            !connector->kdev))
>> +        return -EINVAL;
>> +
>> +    bd = backlight_device_register(name, connector->kdev, connector,
>> +                       &drm_backlight_ops, &props);
>> +    if (IS_ERR(bd))
>> +        return PTR_ERR(bd);
>> +
>> +    connector->backlight = bd;
>> +
>> +    return 0;
>> +}
>> +
>> +/**
>> + * drm_backlight_register() - Register a backlight device for a
>> connector
>> + * @connector: Connector
>> + *
>> + * This function registers a backlight device for @connector with the
>> following
>> + * characteristics:
>> + *
>> + * - The connector sysfs device is used as a parent device for the
>> backlight device.
>> + *   Userspace can use this to connect backlight device and connector.
>> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
>> + * - Type is "raw"
>> + * - Scale is "non-linear" (perceptual)
>> + * - Max brightness is 100 giving a range of 0-100 inclusive
>> + * - Reading sysfs **brightness** returns the backlight device property
>> + * - Reading sysfs **actual_brightness** returns the connector state
>> value
>> + * - Writing sysfs **bl_power** is ignored, the DPMS connector
>> property should
>> + *   be used to control power.
>> + * - Backlight device suspend/resume events are ignored.
>> + *
>> + * Note:
>> + *
>> + * Brightness zero should not turn off backlight it should be the
>> minimum
>> + * brightness, DPMS handles power.
>> + *
>> + * This function must be called from
>> &drm_connector_funcs->late_register() since
>> + * it depends on the sysfs device.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_backlight_register(struct drm_connector *connector)
>> +{
>> +    const char *name = NULL;
>> +    int ret;
>> +
>> +    name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
>> +             connector->dev->primary->index, connector->name);
>> +    if (!name)
>> +        return -ENOMEM;
>> +
>> +    ret = drm_backlight_register_with_name(connector, name);
>> +    kfree(name);
>> +
>> +    return ret;
>> +}
>> +EXPORT_SYMBOL(drm_backlight_register);
>> +
>> +/**
>> + * drm_backlight_unregister() - Unregister backlight device
>> + * @connector: Connector
>> + *
>> + * Unregister a backlight device. This must be called from the
>> + * &drm_connector_funcs->early_unregister() callback.
>> + */
>> +void drm_backlight_unregister(struct drm_connector *connector)
>> +{
>> +    backlight_device_unregister(connector->backlight);
>> +}
>> +EXPORT_SYMBOL(drm_backlight_unregister);
>> diff --git a/include/drm/drm_backlight_helper.h
>> b/include/drm/drm_backlight_helper.h
>> new file mode 100644
>> index 000000000000..4151b66eb0b4
>> --- /dev/null
>> +++ b/include/drm/drm_backlight_helper.h
>> @@ -0,0 +1,9 @@
>> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
>> +
>> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
>> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
>> +
>> +int drm_backlight_register(struct drm_connector *connector);
>> +void drm_backlight_unregister(struct drm_connector *connector);
>> +
>> +#endif
>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>> index 221910948b37..ce678b694f45 100644
>> --- a/include/drm/drm_connector.h
>> +++ b/include/drm/drm_connector.h
>> @@ -32,6 +32,7 @@
>>     #include <uapi/drm/drm_mode.h>
>>   +struct backlight_device;
>>   struct drm_connector_helper_funcs;
>>   struct drm_modeset_acquire_ctx;
>>   struct drm_device;
>> @@ -656,6 +657,12 @@ struct drm_connector_state {
>>        */
>>       u8 max_bpc;
>>   +    /**
>> +     * @backlight_brightness: Brightness value of the connector
>> backlight
>> +     * device. See drm_backlight_register().
>> +     */
>> +    u8 backlight_brightness;
>> +
>>       /**
>>        * @hdr_output_metadata:
>>        * DRM blob property for HDR output metadata
>> @@ -1422,6 +1429,9 @@ struct drm_connector {
>>         /** @hdr_sink_metadata: HDR Metadata Information read from
>> sink */
>>       struct hdr_sink_metadata hdr_sink_metadata;
>> +
>> +    /** @backlight: Backlight device. See drm_backlight_register() */
>> +    struct backlight_device *backlight;
>>   };
>>     #define obj_to_connector(x) container_of(x, struct drm_connector,
>> base)
>>
> 
> 

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

* Re: [PATCH 02/10] drm: Add backlight helper
@ 2020-04-29 18:40       ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-29 18:40 UTC (permalink / raw)
  To: Hans de Goede, dri-devel, linux-usb; +Cc: Daniel Thompson, Christian Kellner



Den 29.04.2020 16.13, skrev Hans de Goede:
> Hi Noralf,
> 
> On 4/29/20 2:48 PM, Noralf Trønnes wrote:
>> This adds a function that creates a backlight device for a connector.
>> It does not deal with the KMS backlight ABI proposition[1] to add a
>> connector property. It only takes the current best practise to
>> standardise
>> the creation of a backlight device for DRM drivers while we wait for the
>> property.
>>
>> The brightness value is set using a connector state variable and an
>> atomic
>> commit.
>>
>> I have looked through some of the backlight users and this is what
>> I've found:
>>
>> GNOME [2]
>> ---------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
> 
> I'm afraid that this is an incaccurate view of how GNOME handles the
> brightness. gnome-settings-daemon (g-s-d) exports a DBUS property which has
> a range of 0 - 100%.  But it also offers step-up and step-down DBUS methods
> which are used for handling brightness hotkey presses.
> 
> This is important because g-s-d internally also keeps a step_size variable
> which depends on the brightness_max value of the sysfs backlight interface,
> like this:
> 
> BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)
> 
> This is important because some older laptops where we depend on the
> vendor specific ACPI method (from e.g. dell-laptop or thinkpad_acpi)
> there are only 8 levels. So if g-s-d where to simply fake a 1-100
> range and would leave the stepping up to the DBus API user and that
> user would want 20 steps, so 5 % per step, then the user would get
> 
> Start      -> 100% -> level 8
> Press down ->  95% -> level 7
> Press down ->  90% -> level 7 *no change*
> etc.
> 
> Somewhat related on some embedded ARM devices there are tricks where
> when the entire scene being rendered does not use 100% white as color,
> the entire scene has all its rgb values upscaled (too a curve) so that
> the brightest colors do hit 100% of one of r/g/b, combined with dimming
> the backlight a bit to save power. As you can imagine for tricks like
> these you want as much backlight control precision as possible.
> 
> So any backlight infra we add must expose the true range of the
> backlight control and not normalize it to a 0-100 range.
> 
> So sorry, but nack for the current version because of the hardcoding
> of the range.

No problem, I just had to start from somewhere, and I started with: Give
the driver developer as few options as possible, no more than necessary,
but I didn't really know what was necessary :-)

The reason I chose a 0-100 range is because the backlight property ABI
proposal had this range and it maps so nicely to percent. And can the
ordinary human see brightness changes in more than 100 steps?

This helper is only to be used by drm drivers and I assumed that all the
current drivers registering a backlight device could at least do that range.

Looking through the drivers and their max_brightness values that
assumption isn't quite right:

amd: 255
gma500: 100
i915: <don't know, register read>
nouveau/nv40: 31
nouveau/nv50: 100
radeon: 255
shmobile: <don't know, from platform data>

panel-dsi-cm.c: 255
panel-jdi-lt070me05000.c: 255
panel-orisetech-otm8009a.c: 255
panel-raydium-rm67191.c: 255
panel-samsung-s6e63m0.c: 10
panel-sony-acx424akp.c: 1023
panel-samsung-s6e3ha2.c: 100
panel-samsung-s6e63j0x03.c: 100
panel-sony-acx565akm.c: 255
bridge/parade-ps8622.c: 255

I'll add max_brightness as an argument together with scale which you
commented on below.

> 
> Also the scale really should be specified by the driver, or be hardcoded
> to BACKLIGHT_SCALE_UNKNOWN for now. In many cases we do not really know.
> But for e.g. the acpi_video firmware backlight interface a good guess is
> that it actually represents a perceptual scale rather then controlling
> the wattage.
> 
> Where as the native i915 backlight interface really is controlling
> the wattage without any perceptual correction.
> 
> Another problem with your proposal is that it seems to assume that
> the backlight is controlled by the drm/kms driver. On x86 we have

Yes, this backlight device is just for drm drivers.
The reason I spend time on this is because I want to pass backlight
brightness changes through the atomic machinery like any other state change.

Noralf.

> atleast 3 different drivers for the backlight:
> 
> 1) The i915 (or amd/nouveau) native driver which more or less
> directly pokes the PWM controller of the GPU.
> 2) The ACPI video standard backlight interface
> 3) Vendor specific ACPI interfaces from older laptops
> 
> ATM we always register 1. which could remain unchanged with
> your code and then also register 2/3 if we (the kernel) think
> that will work better (*) and then rely on userspace prefering
> these (they have a different backlight_type) over 1.
> 
> Ideally any infra we add will also offer the option to tie
> 2. or 3. to the connector...
> 
> Regards,
> 
> Hans
> 
> 
> 
> *) e.g. it will work while the others will not work at all
> 
> 
> 
> 
>>
>> Avoids setting the sysfs brightness value to zero if max_brightness >=
>> 99.
>> Can connect connector and backlight using the sysfs device.
>>
>> KDE [3]
>> -------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
>>
>> Weston [4]
>> ----------
>>
>> Brightness range: 0-255
>> Scale: Assumes perceptual
>>
>> Chromium OS [5]
>> ---------------
>>
>> Brightness range: 0-100
>> Scale: Depends on the sysfs file 'scale' which is a recent addition
>> (2019)
>>
>> xserver [6]
>> -----------
>>
>> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
>> Scale: Assumes perceptual
>>
>> The builtin modesetting driver[7] does not support Backlight, Intel[8]
>> does.
>>
>> [1]
>> https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
>>
>> [2]
>> https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
>>
>> [3]
>> https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
>>
>> [4]
>> https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
>>
>> [5]
>> https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
>>
>> [6]
>> https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
>> [7]
>> https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
>>
>> [8]
>> https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
>>
>>
>> Cc: Hans de Goede <hdegoede@redhat.com>
>> Cc: Jani Nikula <jani.nikula@linux.intel.com>
>> Cc: Martin Peres <martin.peres@linux.intel.com>
>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>   Documentation/gpu/drm-kms-helpers.rst  |   6 +
>>   drivers/gpu/drm/Kconfig                |   7 ++
>>   drivers/gpu/drm/Makefile               |   1 +
>>   drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>>   include/drm/drm_backlight_helper.h     |   9 ++
>>   include/drm/drm_connector.h            |  10 ++
>>   6 files changed, 187 insertions(+)
>>   create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>>   create mode 100644 include/drm/drm_backlight_helper.h
>>
>> diff --git a/Documentation/gpu/drm-kms-helpers.rst
>> b/Documentation/gpu/drm-kms-helpers.rst
>> index 9668a7fe2408..29a2f4b49fd2 100644
>> --- a/Documentation/gpu/drm-kms-helpers.rst
>> +++ b/Documentation/gpu/drm-kms-helpers.rst
>> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>>     .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>>      :export:
>> +
>> +Backlight Helper Reference
>> +==========================
>> +
>> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
>> +   :export:
>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>> index d0aa6cff2e02..f6e13e18c9ca 100644
>> --- a/drivers/gpu/drm/Kconfig
>> +++ b/drivers/gpu/drm/Kconfig
>> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>>       help
>>         Choose this if you need the GEM shmem helper functions
>>   +config DRM_BACKLIGHT_HELPER
>> +    bool
>> +    depends on DRM
>> +    select BACKLIGHT_CLASS_DEVICE
>> +    help
>> +      Choose this if you need the backlight device helper functions
>> +
>>   config DRM_VM
>>       bool
>>       depends on DRM && MMU
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index 6493088a0fdd..0d17662dde0a 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) +=
>> drm_fb_helper.o
>>   drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>>   drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>>   drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
>> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>>     obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>>   obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
>> diff --git a/drivers/gpu/drm/drm_backlight_helper.c
>> b/drivers/gpu/drm/drm_backlight_helper.c
>> new file mode 100644
>> index 000000000000..06e6a75d1d0a
>> --- /dev/null
>> +++ b/drivers/gpu/drm/drm_backlight_helper.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0 OR MIT
>> +/*
>> + * Copyright 2020 Noralf Trønnes
>> + */
>> +
>> +#include <linux/backlight.h>
>> +
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_file.h>
>> +
>> +static int drm_backlight_update_status(struct backlight_device *bd)
>> +{
>> +    struct drm_connector *connector = bl_get_data(bd);
>> +    struct drm_connector_state *connector_state;
>> +    struct drm_device *dev = connector->dev;
>> +    struct drm_modeset_acquire_ctx ctx;
>> +    struct drm_atomic_state *state;
>> +    int ret;
>> +
>> +    state = drm_atomic_state_alloc(dev);
>> +    if (!state)
>> +        return -ENOMEM;
>> +
>> +    drm_modeset_acquire_init(&ctx, 0);
>> +    state->acquire_ctx = &ctx;
>> +retry:
>> +    connector_state = drm_atomic_get_connector_state(state, connector);
>> +    if (IS_ERR(connector_state)) {
>> +        ret = PTR_ERR(connector_state);
>> +        goto out;
>> +    }
>> +
>> +    connector_state->backlight_brightness = bd->props.brightness;
>> +
>> +    ret = drm_atomic_commit(state);
>> +out:
>> +    if (ret == -EDEADLK) {
>> +        drm_atomic_state_clear(state);
>> +        drm_modeset_backoff(&ctx);
>> +        goto retry;
>> +    }
>> +
>> +    drm_atomic_state_put(state);
>> +
>> +    drm_modeset_drop_locks(&ctx);
>> +    drm_modeset_acquire_fini(&ctx);
>> +
>> +    return ret;
>> +}
>> +
>> +static int drm_backlight_get_brightness(struct backlight_device *bd)
>> +{
>> +    struct drm_connector *connector = bl_get_data(bd);
>> +    struct drm_device *dev = connector->dev;
>> +    int brightness;
>> +
>> +    drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
>> +    brightness = connector->state->backlight_brightness;
>> +    drm_modeset_unlock(&dev->mode_config.connection_mutex);
>> +
>> +    return brightness;
>> +}
>> +
>> +static const struct backlight_ops drm_backlight_ops = {
>> +    .get_brightness = drm_backlight_get_brightness,
>> +    .update_status    = drm_backlight_update_status,
>> +};
>> +
>> +/* Can be exported for drivers carrying a legacy name */
>> +static int drm_backlight_register_with_name(struct drm_connector
>> *connector, const char *name)
>> +{
>> +    struct backlight_device *bd;
>> +    const struct backlight_properties props = {
>> +        .type = BACKLIGHT_RAW,
>> +        .scale = BACKLIGHT_SCALE_NON_LINEAR,
>> +        .max_brightness = 100,
>> +    };
>> +
>> +    if (WARN_ON(!drm_core_check_feature(connector->dev,
>> DRIVER_MODESET) ||
>> +            !drm_drv_uses_atomic_modeset(connector->dev) ||
>> +            !connector->kdev))
>> +        return -EINVAL;
>> +
>> +    bd = backlight_device_register(name, connector->kdev, connector,
>> +                       &drm_backlight_ops, &props);
>> +    if (IS_ERR(bd))
>> +        return PTR_ERR(bd);
>> +
>> +    connector->backlight = bd;
>> +
>> +    return 0;
>> +}
>> +
>> +/**
>> + * drm_backlight_register() - Register a backlight device for a
>> connector
>> + * @connector: Connector
>> + *
>> + * This function registers a backlight device for @connector with the
>> following
>> + * characteristics:
>> + *
>> + * - The connector sysfs device is used as a parent device for the
>> backlight device.
>> + *   Userspace can use this to connect backlight device and connector.
>> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
>> + * - Type is "raw"
>> + * - Scale is "non-linear" (perceptual)
>> + * - Max brightness is 100 giving a range of 0-100 inclusive
>> + * - Reading sysfs **brightness** returns the backlight device property
>> + * - Reading sysfs **actual_brightness** returns the connector state
>> value
>> + * - Writing sysfs **bl_power** is ignored, the DPMS connector
>> property should
>> + *   be used to control power.
>> + * - Backlight device suspend/resume events are ignored.
>> + *
>> + * Note:
>> + *
>> + * Brightness zero should not turn off backlight it should be the
>> minimum
>> + * brightness, DPMS handles power.
>> + *
>> + * This function must be called from
>> &drm_connector_funcs->late_register() since
>> + * it depends on the sysfs device.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_backlight_register(struct drm_connector *connector)
>> +{
>> +    const char *name = NULL;
>> +    int ret;
>> +
>> +    name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
>> +             connector->dev->primary->index, connector->name);
>> +    if (!name)
>> +        return -ENOMEM;
>> +
>> +    ret = drm_backlight_register_with_name(connector, name);
>> +    kfree(name);
>> +
>> +    return ret;
>> +}
>> +EXPORT_SYMBOL(drm_backlight_register);
>> +
>> +/**
>> + * drm_backlight_unregister() - Unregister backlight device
>> + * @connector: Connector
>> + *
>> + * Unregister a backlight device. This must be called from the
>> + * &drm_connector_funcs->early_unregister() callback.
>> + */
>> +void drm_backlight_unregister(struct drm_connector *connector)
>> +{
>> +    backlight_device_unregister(connector->backlight);
>> +}
>> +EXPORT_SYMBOL(drm_backlight_unregister);
>> diff --git a/include/drm/drm_backlight_helper.h
>> b/include/drm/drm_backlight_helper.h
>> new file mode 100644
>> index 000000000000..4151b66eb0b4
>> --- /dev/null
>> +++ b/include/drm/drm_backlight_helper.h
>> @@ -0,0 +1,9 @@
>> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
>> +
>> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
>> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
>> +
>> +int drm_backlight_register(struct drm_connector *connector);
>> +void drm_backlight_unregister(struct drm_connector *connector);
>> +
>> +#endif
>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>> index 221910948b37..ce678b694f45 100644
>> --- a/include/drm/drm_connector.h
>> +++ b/include/drm/drm_connector.h
>> @@ -32,6 +32,7 @@
>>     #include <uapi/drm/drm_mode.h>
>>   +struct backlight_device;
>>   struct drm_connector_helper_funcs;
>>   struct drm_modeset_acquire_ctx;
>>   struct drm_device;
>> @@ -656,6 +657,12 @@ struct drm_connector_state {
>>        */
>>       u8 max_bpc;
>>   +    /**
>> +     * @backlight_brightness: Brightness value of the connector
>> backlight
>> +     * device. See drm_backlight_register().
>> +     */
>> +    u8 backlight_brightness;
>> +
>>       /**
>>        * @hdr_output_metadata:
>>        * DRM blob property for HDR output metadata
>> @@ -1422,6 +1429,9 @@ struct drm_connector {
>>         /** @hdr_sink_metadata: HDR Metadata Information read from
>> sink */
>>       struct hdr_sink_metadata hdr_sink_metadata;
>> +
>> +    /** @backlight: Backlight device. See drm_backlight_register() */
>> +    struct backlight_device *backlight;
>>   };
>>     #define obj_to_connector(x) container_of(x, struct drm_connector,
>> base)
>>
> 
> 
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-04-30  8:32     ` Lee Jones
  -1 siblings, 0 replies; 66+ messages in thread
From: Lee Jones @ 2020-04-30  8:32 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-usb, Daniel Thompson, Jingoo Han

On Wed, 29 Apr 2020, Noralf Trønnes wrote:

> Add a way to lookup a backlight device based on its name.
> Will be used by a USB display gadget getting the name from configfs.
> 
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Cc: Jingoo Han <jingoohan1@gmail.com>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>  include/linux/backlight.h           |  1 +
>  2 files changed, 22 insertions(+)

Once reviewed, can this patch be applied on its own?

My guess is that it can't, as the other patches in this set depend on
it, right?  If this assumption is true, you need to send me the rest
of the set.

FYI: It's normally better to send the whole set to everyone, as it
provides visibility on current review (or lack there of) status of the
other patches and allows each of the maintainers to discuss possible
merge strategies.

> diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
> index cac3e35d7630..92d80aa0c0ef 100644
> --- a/drivers/video/backlight/backlight.c
> +++ b/drivers/video/backlight/backlight.c
> @@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
>  }
>  EXPORT_SYMBOL(backlight_device_get_by_type);
>  
> +/**
> + * backlight_device_get_by_name - Get backlight device by name
> + * @name: Device name
> + *
> + * This function looks up a backlight device by its name. It obtains a reference
> + * on the backlight device and it is the caller's responsibility to drop the
> + * reference by calling backlight_put().
> + *
> + * Returns:
> + * A pointer to the backlight device if found, otherwise NULL.
> + */
> +struct backlight_device *backlight_device_get_by_name(const char *name)
> +{
> +	struct device *dev;
> +
> +	dev = class_find_device_by_name(backlight_class, name);
> +
> +	return dev ? to_backlight_device(dev) : NULL;
> +}
> +EXPORT_SYMBOL(backlight_device_get_by_name);
> +
>  /**
>   * backlight_device_unregister - unregisters a backlight device object.
>   * @bd: the backlight device object to be unregistered and freed.
> diff --git a/include/linux/backlight.h b/include/linux/backlight.h
> index c7d6b2e8c3b5..56e4580d4f55 100644
> --- a/include/linux/backlight.h
> +++ b/include/linux/backlight.h
> @@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
>  extern int backlight_register_notifier(struct notifier_block *nb);
>  extern int backlight_unregister_notifier(struct notifier_block *nb);
>  extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
> +struct backlight_device *backlight_device_get_by_name(const char *name);
>  extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
>  
>  #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-04-30  8:32     ` Lee Jones
  0 siblings, 0 replies; 66+ messages in thread
From: Lee Jones @ 2020-04-30  8:32 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: Jingoo Han, Daniel Thompson, linux-usb, dri-devel

On Wed, 29 Apr 2020, Noralf Trønnes wrote:

> Add a way to lookup a backlight device based on its name.
> Will be used by a USB display gadget getting the name from configfs.
> 
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Cc: Jingoo Han <jingoohan1@gmail.com>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>  include/linux/backlight.h           |  1 +
>  2 files changed, 22 insertions(+)

Once reviewed, can this patch be applied on its own?

My guess is that it can't, as the other patches in this set depend on
it, right?  If this assumption is true, you need to send me the rest
of the set.

FYI: It's normally better to send the whole set to everyone, as it
provides visibility on current review (or lack there of) status of the
other patches and allows each of the maintainers to discuss possible
merge strategies.

> diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
> index cac3e35d7630..92d80aa0c0ef 100644
> --- a/drivers/video/backlight/backlight.c
> +++ b/drivers/video/backlight/backlight.c
> @@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
>  }
>  EXPORT_SYMBOL(backlight_device_get_by_type);
>  
> +/**
> + * backlight_device_get_by_name - Get backlight device by name
> + * @name: Device name
> + *
> + * This function looks up a backlight device by its name. It obtains a reference
> + * on the backlight device and it is the caller's responsibility to drop the
> + * reference by calling backlight_put().
> + *
> + * Returns:
> + * A pointer to the backlight device if found, otherwise NULL.
> + */
> +struct backlight_device *backlight_device_get_by_name(const char *name)
> +{
> +	struct device *dev;
> +
> +	dev = class_find_device_by_name(backlight_class, name);
> +
> +	return dev ? to_backlight_device(dev) : NULL;
> +}
> +EXPORT_SYMBOL(backlight_device_get_by_name);
> +
>  /**
>   * backlight_device_unregister - unregisters a backlight device object.
>   * @bd: the backlight device object to be unregistered and freed.
> diff --git a/include/linux/backlight.h b/include/linux/backlight.h
> index c7d6b2e8c3b5..56e4580d4f55 100644
> --- a/include/linux/backlight.h
> +++ b/include/linux/backlight.h
> @@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
>  extern int backlight_register_notifier(struct notifier_block *nb);
>  extern int backlight_unregister_notifier(struct notifier_block *nb);
>  extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
> +struct backlight_device *backlight_device_get_by_name(const char *name);
>  extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
>  
>  #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-30  8:32     ` Lee Jones
@ 2020-04-30  9:20       ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-30  9:20 UTC (permalink / raw)
  To: Lee Jones; +Cc: dri-devel, linux-usb, Daniel Thompson, Jingoo Han



Den 30.04.2020 10.32, skrev Lee Jones:
> On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> 
>> Add a way to lookup a backlight device based on its name.
>> Will be used by a USB display gadget getting the name from configfs.
>>
>> Cc: Lee Jones <lee.jones@linaro.org>
>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>> Cc: Jingoo Han <jingoohan1@gmail.com>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>>  include/linux/backlight.h           |  1 +
>>  2 files changed, 22 insertions(+)
> 
> Once reviewed, can this patch be applied on its own?
> 

If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
the driver won't be ready for that. This patch has this dependency
chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
gets easier.

> My guess is that it can't, as the other patches in this set depend on
> it, right?  If this assumption is true, you need to send me the rest
> of the set.
> 
> FYI: It's normally better to send the whole set to everyone, as it
> provides visibility on current review (or lack there of) status of the
> other patches and allows each of the maintainers to discuss possible
> merge strategies.

dri-devel is the ML for backlight so I assumed you got the full set.
I have had trouble in the past with my email provider dropping parts of
a series when I had to many recipients.

Noralf.

> 
>> diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
>> index cac3e35d7630..92d80aa0c0ef 100644
>> --- a/drivers/video/backlight/backlight.c
>> +++ b/drivers/video/backlight/backlight.c
>> @@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
>>  }
>>  EXPORT_SYMBOL(backlight_device_get_by_type);
>>  
>> +/**
>> + * backlight_device_get_by_name - Get backlight device by name
>> + * @name: Device name
>> + *
>> + * This function looks up a backlight device by its name. It obtains a reference
>> + * on the backlight device and it is the caller's responsibility to drop the
>> + * reference by calling backlight_put().
>> + *
>> + * Returns:
>> + * A pointer to the backlight device if found, otherwise NULL.
>> + */
>> +struct backlight_device *backlight_device_get_by_name(const char *name)
>> +{
>> +	struct device *dev;
>> +
>> +	dev = class_find_device_by_name(backlight_class, name);
>> +
>> +	return dev ? to_backlight_device(dev) : NULL;
>> +}
>> +EXPORT_SYMBOL(backlight_device_get_by_name);
>> +
>>  /**
>>   * backlight_device_unregister - unregisters a backlight device object.
>>   * @bd: the backlight device object to be unregistered and freed.
>> diff --git a/include/linux/backlight.h b/include/linux/backlight.h
>> index c7d6b2e8c3b5..56e4580d4f55 100644
>> --- a/include/linux/backlight.h
>> +++ b/include/linux/backlight.h
>> @@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
>>  extern int backlight_register_notifier(struct notifier_block *nb);
>>  extern int backlight_unregister_notifier(struct notifier_block *nb);
>>  extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
>> +struct backlight_device *backlight_device_get_by_name(const char *name);
>>  extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
>>  
>>  #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)
> 

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-04-30  9:20       ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-30  9:20 UTC (permalink / raw)
  To: Lee Jones; +Cc: Jingoo Han, Daniel Thompson, linux-usb, dri-devel



Den 30.04.2020 10.32, skrev Lee Jones:
> On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> 
>> Add a way to lookup a backlight device based on its name.
>> Will be used by a USB display gadget getting the name from configfs.
>>
>> Cc: Lee Jones <lee.jones@linaro.org>
>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>> Cc: Jingoo Han <jingoohan1@gmail.com>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>>  include/linux/backlight.h           |  1 +
>>  2 files changed, 22 insertions(+)
> 
> Once reviewed, can this patch be applied on its own?
> 

If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
the driver won't be ready for that. This patch has this dependency
chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
gets easier.

> My guess is that it can't, as the other patches in this set depend on
> it, right?  If this assumption is true, you need to send me the rest
> of the set.
> 
> FYI: It's normally better to send the whole set to everyone, as it
> provides visibility on current review (or lack there of) status of the
> other patches and allows each of the maintainers to discuss possible
> merge strategies.

dri-devel is the ML for backlight so I assumed you got the full set.
I have had trouble in the past with my email provider dropping parts of
a series when I had to many recipients.

Noralf.

> 
>> diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
>> index cac3e35d7630..92d80aa0c0ef 100644
>> --- a/drivers/video/backlight/backlight.c
>> +++ b/drivers/video/backlight/backlight.c
>> @@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
>>  }
>>  EXPORT_SYMBOL(backlight_device_get_by_type);
>>  
>> +/**
>> + * backlight_device_get_by_name - Get backlight device by name
>> + * @name: Device name
>> + *
>> + * This function looks up a backlight device by its name. It obtains a reference
>> + * on the backlight device and it is the caller's responsibility to drop the
>> + * reference by calling backlight_put().
>> + *
>> + * Returns:
>> + * A pointer to the backlight device if found, otherwise NULL.
>> + */
>> +struct backlight_device *backlight_device_get_by_name(const char *name)
>> +{
>> +	struct device *dev;
>> +
>> +	dev = class_find_device_by_name(backlight_class, name);
>> +
>> +	return dev ? to_backlight_device(dev) : NULL;
>> +}
>> +EXPORT_SYMBOL(backlight_device_get_by_name);
>> +
>>  /**
>>   * backlight_device_unregister - unregisters a backlight device object.
>>   * @bd: the backlight device object to be unregistered and freed.
>> diff --git a/include/linux/backlight.h b/include/linux/backlight.h
>> index c7d6b2e8c3b5..56e4580d4f55 100644
>> --- a/include/linux/backlight.h
>> +++ b/include/linux/backlight.h
>> @@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
>>  extern int backlight_register_notifier(struct notifier_block *nb);
>>  extern int backlight_unregister_notifier(struct notifier_block *nb);
>>  extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
>> +struct backlight_device *backlight_device_get_by_name(const char *name);
>>  extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
>>  
>>  #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)
> 
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-30  9:20       ` Noralf Trønnes
@ 2020-04-30 10:15         ` Lee Jones
  -1 siblings, 0 replies; 66+ messages in thread
From: Lee Jones @ 2020-04-30 10:15 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-usb, Daniel Thompson, Jingoo Han

On Thu, 30 Apr 2020, Noralf Trønnes wrote:

> 
> 
> Den 30.04.2020 10.32, skrev Lee Jones:
> > On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> > 
> >> Add a way to lookup a backlight device based on its name.
> >> Will be used by a USB display gadget getting the name from configfs.
> >>
> >> Cc: Lee Jones <lee.jones@linaro.org>
> >> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> >> Cc: Jingoo Han <jingoohan1@gmail.com>
> >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> >> ---
> >>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
> >>  include/linux/backlight.h           |  1 +
> >>  2 files changed, 22 insertions(+)
> > 
> > Once reviewed, can this patch be applied on its own?
> > 
> 
> If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
> the driver won't be ready for that. This patch has this dependency
> chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
> gets easier.
> 
> > My guess is that it can't, as the other patches in this set depend on
> > it, right?  If this assumption is true, you need to send me the rest
> > of the set.
> > 
> > FYI: It's normally better to send the whole set to everyone, as it
> > provides visibility on current review (or lack there of) status of the
> > other patches and allows each of the maintainers to discuss possible
> > merge strategies.
> 
> dri-devel is the ML for backlight so I assumed you got the full set.

dri-devel isn't the ML for Backlight.  It's an interested party.

I certainly have no intention of subscribing to it.

> I have had trouble in the past with my email provider dropping parts of
> a series when I had to many recipients.

Without visibility into the other patches in the set, things become
more difficult.  Maybe use a different/better email provider.

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-04-30 10:15         ` Lee Jones
  0 siblings, 0 replies; 66+ messages in thread
From: Lee Jones @ 2020-04-30 10:15 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: Jingoo Han, Daniel Thompson, linux-usb, dri-devel

On Thu, 30 Apr 2020, Noralf Trønnes wrote:

> 
> 
> Den 30.04.2020 10.32, skrev Lee Jones:
> > On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> > 
> >> Add a way to lookup a backlight device based on its name.
> >> Will be used by a USB display gadget getting the name from configfs.
> >>
> >> Cc: Lee Jones <lee.jones@linaro.org>
> >> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> >> Cc: Jingoo Han <jingoohan1@gmail.com>
> >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> >> ---
> >>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
> >>  include/linux/backlight.h           |  1 +
> >>  2 files changed, 22 insertions(+)
> > 
> > Once reviewed, can this patch be applied on its own?
> > 
> 
> If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
> the driver won't be ready for that. This patch has this dependency
> chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
> gets easier.
> 
> > My guess is that it can't, as the other patches in this set depend on
> > it, right?  If this assumption is true, you need to send me the rest
> > of the set.
> > 
> > FYI: It's normally better to send the whole set to everyone, as it
> > provides visibility on current review (or lack there of) status of the
> > other patches and allows each of the maintainers to discuss possible
> > merge strategies.
> 
> dri-devel is the ML for backlight so I assumed you got the full set.

dri-devel isn't the ML for Backlight.  It's an interested party.

I certainly have no intention of subscribing to it.

> I have had trouble in the past with my email provider dropping parts of
> a series when I had to many recipients.

Without visibility into the other patches in the set, things become
more difficult.  Maybe use a different/better email provider.

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-30 10:15         ` Lee Jones
@ 2020-04-30 10:42           ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-30 10:42 UTC (permalink / raw)
  To: Lee Jones; +Cc: dri-devel, linux-usb, Daniel Thompson, Jingoo Han



Den 30.04.2020 12.15, skrev Lee Jones:
> On Thu, 30 Apr 2020, Noralf Trønnes wrote:
> 
>>
>>
>> Den 30.04.2020 10.32, skrev Lee Jones:
>>> On Wed, 29 Apr 2020, Noralf Trønnes wrote:
>>>
>>>> Add a way to lookup a backlight device based on its name.
>>>> Will be used by a USB display gadget getting the name from configfs.
>>>>
>>>> Cc: Lee Jones <lee.jones@linaro.org>
>>>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>>>> Cc: Jingoo Han <jingoohan1@gmail.com>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>>> ---
>>>>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>>>>  include/linux/backlight.h           |  1 +
>>>>  2 files changed, 22 insertions(+)
>>>
>>> Once reviewed, can this patch be applied on its own?
>>>
>>
>> If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
>> the driver won't be ready for that. This patch has this dependency
>> chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
>> gets easier.
>>
>>> My guess is that it can't, as the other patches in this set depend on
>>> it, right?  If this assumption is true, you need to send me the rest
>>> of the set.
>>>
>>> FYI: It's normally better to send the whole set to everyone, as it
>>> provides visibility on current review (or lack there of) status of the
>>> other patches and allows each of the maintainers to discuss possible
>>> merge strategies.
>>
>> dri-devel is the ML for backlight so I assumed you got the full set.
> 
> dri-devel isn't the ML for Backlight.  It's an interested party.

Oh, I thought it was strange, but kernel development has all kinds of
things that seems strange to me, so I just went with it.

> 
> I certainly have no intention of subscribing to it.
> 
>> I have had trouble in the past with my email provider dropping parts of
>> a series when I had to many recipients.
> 
> Without visibility into the other patches in the set, things become
> more difficult.  Maybe use a different/better email provider.
> 

Yeah, you need to have context, I have resent the series to you, Daniel
and Jingoo.

Noralf.

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-04-30 10:42           ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-04-30 10:42 UTC (permalink / raw)
  To: Lee Jones; +Cc: Jingoo Han, Daniel Thompson, linux-usb, dri-devel



Den 30.04.2020 12.15, skrev Lee Jones:
> On Thu, 30 Apr 2020, Noralf Trønnes wrote:
> 
>>
>>
>> Den 30.04.2020 10.32, skrev Lee Jones:
>>> On Wed, 29 Apr 2020, Noralf Trønnes wrote:
>>>
>>>> Add a way to lookup a backlight device based on its name.
>>>> Will be used by a USB display gadget getting the name from configfs.
>>>>
>>>> Cc: Lee Jones <lee.jones@linaro.org>
>>>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>>>> Cc: Jingoo Han <jingoohan1@gmail.com>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>>> ---
>>>>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>>>>  include/linux/backlight.h           |  1 +
>>>>  2 files changed, 22 insertions(+)
>>>
>>> Once reviewed, can this patch be applied on its own?
>>>
>>
>> If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
>> the driver won't be ready for that. This patch has this dependency
>> chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
>> gets easier.
>>
>>> My guess is that it can't, as the other patches in this set depend on
>>> it, right?  If this assumption is true, you need to send me the rest
>>> of the set.
>>>
>>> FYI: It's normally better to send the whole set to everyone, as it
>>> provides visibility on current review (or lack there of) status of the
>>> other patches and allows each of the maintainers to discuss possible
>>> merge strategies.
>>
>> dri-devel is the ML for backlight so I assumed you got the full set.
> 
> dri-devel isn't the ML for Backlight.  It's an interested party.

Oh, I thought it was strange, but kernel development has all kinds of
things that seems strange to me, so I just went with it.

> 
> I certainly have no intention of subscribing to it.
> 
>> I have had trouble in the past with my email provider dropping parts of
>> a series when I had to many recipients.
> 
> Without visibility into the other patches in the set, things become
> more difficult.  Maybe use a different/better email provider.
> 

Yeah, you need to have context, I have resent the series to you, Daniel
and Jingoo.

Noralf.
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-30 10:15         ` Lee Jones
@ 2020-04-30 14:02           ` Daniel Vetter
  -1 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2020-04-30 14:02 UTC (permalink / raw)
  To: Lee Jones
  Cc: Noralf Trønnes, Jingoo Han, Daniel Thompson, linux-usb, dri-devel

On Thu, Apr 30, 2020 at 11:15:29AM +0100, Lee Jones wrote:
> On Thu, 30 Apr 2020, Noralf Trønnes wrote:
> 
> > 
> > 
> > Den 30.04.2020 10.32, skrev Lee Jones:
> > > On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> > > 
> > >> Add a way to lookup a backlight device based on its name.
> > >> Will be used by a USB display gadget getting the name from configfs.
> > >>
> > >> Cc: Lee Jones <lee.jones@linaro.org>
> > >> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> > >> Cc: Jingoo Han <jingoohan1@gmail.com>
> > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> > >> ---
> > >>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
> > >>  include/linux/backlight.h           |  1 +
> > >>  2 files changed, 22 insertions(+)
> > > 
> > > Once reviewed, can this patch be applied on its own?
> > > 
> > 
> > If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
> > the driver won't be ready for that. This patch has this dependency
> > chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
> > gets easier.
> > 
> > > My guess is that it can't, as the other patches in this set depend on
> > > it, right?  If this assumption is true, you need to send me the rest
> > > of the set.
> > > 
> > > FYI: It's normally better to send the whole set to everyone, as it
> > > provides visibility on current review (or lack there of) status of the
> > > other patches and allows each of the maintainers to discuss possible
> > > merge strategies.

Unfortunately this doesn't hold universally, since once you cc too many
people smtp servers start throwing your mails away. Generally only happens
for bigger refactorings, so pretty much anyone working cross-tree doesn't
do this because it doesn't work.

> > dri-devel is the ML for backlight so I assumed you got the full set.
> 
> dri-devel isn't the ML for Backlight.  It's an interested party.
> 
> I certainly have no intention of subscribing to it.

dri-devel is on lore so that you can grab missing patches. No need to
subscribe. I've only manged to get this sorted recently (last autumn or
so), but it's finally done.
-Daniel

> > I have had trouble in the past with my email provider dropping parts of
> > a series when I had to many recipients.
> 
> Without visibility into the other patches in the set, things become
> more difficult.  Maybe use a different/better email provider.
> 
> -- 
> Lee Jones [李琼斯]
> Linaro Services Technical Lead
> Linaro.org │ Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog
> _______________________________________________
> 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] 66+ messages in thread

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-04-30 14:02           ` Daniel Vetter
  0 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2020-04-30 14:02 UTC (permalink / raw)
  To: Lee Jones; +Cc: Jingoo Han, Daniel Thompson, linux-usb, dri-devel

On Thu, Apr 30, 2020 at 11:15:29AM +0100, Lee Jones wrote:
> On Thu, 30 Apr 2020, Noralf Trønnes wrote:
> 
> > 
> > 
> > Den 30.04.2020 10.32, skrev Lee Jones:
> > > On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> > > 
> > >> Add a way to lookup a backlight device based on its name.
> > >> Will be used by a USB display gadget getting the name from configfs.
> > >>
> > >> Cc: Lee Jones <lee.jones@linaro.org>
> > >> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> > >> Cc: Jingoo Han <jingoohan1@gmail.com>
> > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> > >> ---
> > >>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
> > >>  include/linux/backlight.h           |  1 +
> > >>  2 files changed, 22 insertions(+)
> > > 
> > > Once reviewed, can this patch be applied on its own?
> > > 
> > 
> > If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
> > the driver won't be ready for that. This patch has this dependency
> > chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
> > gets easier.
> > 
> > > My guess is that it can't, as the other patches in this set depend on
> > > it, right?  If this assumption is true, you need to send me the rest
> > > of the set.
> > > 
> > > FYI: It's normally better to send the whole set to everyone, as it
> > > provides visibility on current review (or lack there of) status of the
> > > other patches and allows each of the maintainers to discuss possible
> > > merge strategies.

Unfortunately this doesn't hold universally, since once you cc too many
people smtp servers start throwing your mails away. Generally only happens
for bigger refactorings, so pretty much anyone working cross-tree doesn't
do this because it doesn't work.

> > dri-devel is the ML for backlight so I assumed you got the full set.
> 
> dri-devel isn't the ML for Backlight.  It's an interested party.
> 
> I certainly have no intention of subscribing to it.

dri-devel is on lore so that you can grab missing patches. No need to
subscribe. I've only manged to get this sorted recently (last autumn or
so), but it's finally done.
-Daniel

> > I have had trouble in the past with my email provider dropping parts of
> > a series when I had to many recipients.
> 
> Without visibility into the other patches in the set, things become
> more difficult.  Maybe use a different/better email provider.
> 
> -- 
> Lee Jones [李琼斯]
> Linaro Services Technical Lead
> Linaro.org │ Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog
> _______________________________________________
> 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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 02/10] drm: Add backlight helper
  2020-04-29 18:40       ` Noralf Trønnes
@ 2020-04-30 15:36         ` Hans de Goede
  -1 siblings, 0 replies; 66+ messages in thread
From: Hans de Goede @ 2020-04-30 15:36 UTC (permalink / raw)
  To: Noralf Trønnes, dri-devel, linux-usb
  Cc: Jani Nikula, Martin Peres, Daniel Thompson, Christian Kellner

Hi,

On 4/29/20 8:40 PM, Noralf Trønnes wrote:
> 
> 
> Den 29.04.2020 16.13, skrev Hans de Goede:
>> Hi Noralf,
>>
>> On 4/29/20 2:48 PM, Noralf Trønnes wrote:
>>> This adds a function that creates a backlight device for a connector.
>>> It does not deal with the KMS backlight ABI proposition[1] to add a
>>> connector property. It only takes the current best practise to
>>> standardise
>>> the creation of a backlight device for DRM drivers while we wait for the
>>> property.
>>>
>>> The brightness value is set using a connector state variable and an
>>> atomic
>>> commit.
>>>
>>> I have looked through some of the backlight users and this is what
>>> I've found:
>>>
>>> GNOME [2]
>>> ---------
>>>
>>> Brightness range: 0-100
>>> Scale: Assumes perceptual
>>
>> I'm afraid that this is an incaccurate view of how GNOME handles the
>> brightness. gnome-settings-daemon (g-s-d) exports a DBUS property which has
>> a range of 0 - 100%.  But it also offers step-up and step-down DBUS methods
>> which are used for handling brightness hotkey presses.
>>
>> This is important because g-s-d internally also keeps a step_size variable
>> which depends on the brightness_max value of the sysfs backlight interface,
>> like this:
>>
>> BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)
>>
>> This is important because some older laptops where we depend on the
>> vendor specific ACPI method (from e.g. dell-laptop or thinkpad_acpi)
>> there are only 8 levels. So if g-s-d where to simply fake a 1-100
>> range and would leave the stepping up to the DBus API user and that
>> user would want 20 steps, so 5 % per step, then the user would get
>>
>> Start      -> 100% -> level 8
>> Press down ->  95% -> level 7
>> Press down ->  90% -> level 7 *no change*
>> etc.
>>
>> Somewhat related on some embedded ARM devices there are tricks where
>> when the entire scene being rendered does not use 100% white as color,
>> the entire scene has all its rgb values upscaled (too a curve) so that
>> the brightest colors do hit 100% of one of r/g/b, combined with dimming
>> the backlight a bit to save power. As you can imagine for tricks like
>> these you want as much backlight control precision as possible.
>>
>> So any backlight infra we add must expose the true range of the
>> backlight control and not normalize it to a 0-100 range.
>>
>> So sorry, but nack for the current version because of the hardcoding
>> of the range.
> 
> No problem, I just had to start from somewhere, and I started with: Give
> the driver developer as few options as possible, no more than necessary,
> but I didn't really know what was necessary :-)
> 
> The reason I chose a 0-100 range is because the backlight property ABI
> proposal had this range and it maps so nicely to percent. And can the
> ordinary human see brightness changes in more than 100 steps?
> 
> This helper is only to be used by drm drivers and I assumed that all the
> current drivers registering a backlight device could at least do that range.
> 
> Looking through the drivers and their max_brightness values that
> assumption isn't quite right:
> 
> amd: 255
> gma500: 100
> i915: <don't know, register read>
> nouveau/nv40: 31
> nouveau/nv50: 100
> radeon: 255
> shmobile: <don't know, from platform data>
> 
> panel-dsi-cm.c: 255
> panel-jdi-lt070me05000.c: 255
> panel-orisetech-otm8009a.c: 255
> panel-raydium-rm67191.c: 255
> panel-samsung-s6e63m0.c: 10
> panel-sony-acx424akp.c: 1023
> panel-samsung-s6e3ha2.c: 100
> panel-samsung-s6e63j0x03.c: 100
> panel-sony-acx565akm.c: 255
> bridge/parade-ps8622.c: 255
> 
> I'll add max_brightness as an argument together with scale which you
> commented on below.

Ok, sounds good.

>> Also the scale really should be specified by the driver, or be hardcoded
>> to BACKLIGHT_SCALE_UNKNOWN for now. In many cases we do not really know.
>> But for e.g. the acpi_video firmware backlight interface a good guess is
>> that it actually represents a perceptual scale rather then controlling
>> the wattage.
>>
>> Where as the native i915 backlight interface really is controlling
>> the wattage without any perceptual correction.
>>
>> Another problem with your proposal is that it seems to assume that
>> the backlight is controlled by the drm/kms driver. On x86 we have
> 
> Yes, this backlight device is just for drm drivers.

I was hoping you might have some clever ideas how to deal with /
prepare for the backlight driver outside of drm-driver case too.
But I completely understand that you want to limit your scope.

> The reason I spend time on this is because I want to pass backlight
> brightness changes through the atomic machinery like any other state change.

I understand.

Regards,

Hans











>> atleast 3 different drivers for the backlight:
>>
>> 1) The i915 (or amd/nouveau) native driver which more or less
>> directly pokes the PWM controller of the GPU.
>> 2) The ACPI video standard backlight interface
>> 3) Vendor specific ACPI interfaces from older laptops
>>
>> ATM we always register 1. which could remain unchanged with
>> your code and then also register 2/3 if we (the kernel) think
>> that will work better (*) and then rely on userspace prefering
>> these (they have a different backlight_type) over 1.
>>
>> Ideally any infra we add will also offer the option to tie
>> 2. or 3. to the connector...
>>
>> Regards,
>>
>> Hans
>>
>>
>>
>> *) e.g. it will work while the others will not work at all
>>
>>
>>
>>
>>>
>>> Avoids setting the sysfs brightness value to zero if max_brightness >=
>>> 99.
>>> Can connect connector and backlight using the sysfs device.
>>>
>>> KDE [3]
>>> -------
>>>
>>> Brightness range: 0-100
>>> Scale: Assumes perceptual
>>>
>>> Weston [4]
>>> ----------
>>>
>>> Brightness range: 0-255
>>> Scale: Assumes perceptual
>>>
>>> Chromium OS [5]
>>> ---------------
>>>
>>> Brightness range: 0-100
>>> Scale: Depends on the sysfs file 'scale' which is a recent addition
>>> (2019)
>>>
>>> xserver [6]
>>> -----------
>>>
>>> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
>>> Scale: Assumes perceptual
>>>
>>> The builtin modesetting driver[7] does not support Backlight, Intel[8]
>>> does.
>>>
>>> [1]
>>> https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
>>>
>>> [2]
>>> https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
>>>
>>> [3]
>>> https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
>>>
>>> [4]
>>> https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
>>>
>>> [5]
>>> https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
>>>
>>> [6]
>>> https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
>>> [7]
>>> https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
>>>
>>> [8]
>>> https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
>>>
>>>
>>> Cc: Hans de Goede <hdegoede@redhat.com>
>>> Cc: Jani Nikula <jani.nikula@linux.intel.com>
>>> Cc: Martin Peres <martin.peres@linux.intel.com>
>>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>> ---
>>>    Documentation/gpu/drm-kms-helpers.rst  |   6 +
>>>    drivers/gpu/drm/Kconfig                |   7 ++
>>>    drivers/gpu/drm/Makefile               |   1 +
>>>    drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>>>    include/drm/drm_backlight_helper.h     |   9 ++
>>>    include/drm/drm_connector.h            |  10 ++
>>>    6 files changed, 187 insertions(+)
>>>    create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>>>    create mode 100644 include/drm/drm_backlight_helper.h
>>>
>>> diff --git a/Documentation/gpu/drm-kms-helpers.rst
>>> b/Documentation/gpu/drm-kms-helpers.rst
>>> index 9668a7fe2408..29a2f4b49fd2 100644
>>> --- a/Documentation/gpu/drm-kms-helpers.rst
>>> +++ b/Documentation/gpu/drm-kms-helpers.rst
>>> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>>>      .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>>>       :export:
>>> +
>>> +Backlight Helper Reference
>>> +==========================
>>> +
>>> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
>>> +   :export:
>>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>>> index d0aa6cff2e02..f6e13e18c9ca 100644
>>> --- a/drivers/gpu/drm/Kconfig
>>> +++ b/drivers/gpu/drm/Kconfig
>>> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>>>        help
>>>          Choose this if you need the GEM shmem helper functions
>>>    +config DRM_BACKLIGHT_HELPER
>>> +    bool
>>> +    depends on DRM
>>> +    select BACKLIGHT_CLASS_DEVICE
>>> +    help
>>> +      Choose this if you need the backlight device helper functions
>>> +
>>>    config DRM_VM
>>>        bool
>>>        depends on DRM && MMU
>>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>>> index 6493088a0fdd..0d17662dde0a 100644
>>> --- a/drivers/gpu/drm/Makefile
>>> +++ b/drivers/gpu/drm/Makefile
>>> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) +=
>>> drm_fb_helper.o
>>>    drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>>>    drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>>>    drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
>>> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>>>      obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>>>    obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
>>> diff --git a/drivers/gpu/drm/drm_backlight_helper.c
>>> b/drivers/gpu/drm/drm_backlight_helper.c
>>> new file mode 100644
>>> index 000000000000..06e6a75d1d0a
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/drm_backlight_helper.c
>>> @@ -0,0 +1,154 @@
>>> +// SPDX-License-Identifier: GPL-2.0 OR MIT
>>> +/*
>>> + * Copyright 2020 Noralf Trønnes
>>> + */
>>> +
>>> +#include <linux/backlight.h>
>>> +
>>> +#include <drm/drm_atomic.h>
>>> +#include <drm/drm_connector.h>
>>> +#include <drm/drm_drv.h>
>>> +#include <drm/drm_file.h>
>>> +
>>> +static int drm_backlight_update_status(struct backlight_device *bd)
>>> +{
>>> +    struct drm_connector *connector = bl_get_data(bd);
>>> +    struct drm_connector_state *connector_state;
>>> +    struct drm_device *dev = connector->dev;
>>> +    struct drm_modeset_acquire_ctx ctx;
>>> +    struct drm_atomic_state *state;
>>> +    int ret;
>>> +
>>> +    state = drm_atomic_state_alloc(dev);
>>> +    if (!state)
>>> +        return -ENOMEM;
>>> +
>>> +    drm_modeset_acquire_init(&ctx, 0);
>>> +    state->acquire_ctx = &ctx;
>>> +retry:
>>> +    connector_state = drm_atomic_get_connector_state(state, connector);
>>> +    if (IS_ERR(connector_state)) {
>>> +        ret = PTR_ERR(connector_state);
>>> +        goto out;
>>> +    }
>>> +
>>> +    connector_state->backlight_brightness = bd->props.brightness;
>>> +
>>> +    ret = drm_atomic_commit(state);
>>> +out:
>>> +    if (ret == -EDEADLK) {
>>> +        drm_atomic_state_clear(state);
>>> +        drm_modeset_backoff(&ctx);
>>> +        goto retry;
>>> +    }
>>> +
>>> +    drm_atomic_state_put(state);
>>> +
>>> +    drm_modeset_drop_locks(&ctx);
>>> +    drm_modeset_acquire_fini(&ctx);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int drm_backlight_get_brightness(struct backlight_device *bd)
>>> +{
>>> +    struct drm_connector *connector = bl_get_data(bd);
>>> +    struct drm_device *dev = connector->dev;
>>> +    int brightness;
>>> +
>>> +    drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
>>> +    brightness = connector->state->backlight_brightness;
>>> +    drm_modeset_unlock(&dev->mode_config.connection_mutex);
>>> +
>>> +    return brightness;
>>> +}
>>> +
>>> +static const struct backlight_ops drm_backlight_ops = {
>>> +    .get_brightness = drm_backlight_get_brightness,
>>> +    .update_status    = drm_backlight_update_status,
>>> +};
>>> +
>>> +/* Can be exported for drivers carrying a legacy name */
>>> +static int drm_backlight_register_with_name(struct drm_connector
>>> *connector, const char *name)
>>> +{
>>> +    struct backlight_device *bd;
>>> +    const struct backlight_properties props = {
>>> +        .type = BACKLIGHT_RAW,
>>> +        .scale = BACKLIGHT_SCALE_NON_LINEAR,
>>> +        .max_brightness = 100,
>>> +    };
>>> +
>>> +    if (WARN_ON(!drm_core_check_feature(connector->dev,
>>> DRIVER_MODESET) ||
>>> +            !drm_drv_uses_atomic_modeset(connector->dev) ||
>>> +            !connector->kdev))
>>> +        return -EINVAL;
>>> +
>>> +    bd = backlight_device_register(name, connector->kdev, connector,
>>> +                       &drm_backlight_ops, &props);
>>> +    if (IS_ERR(bd))
>>> +        return PTR_ERR(bd);
>>> +
>>> +    connector->backlight = bd;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/**
>>> + * drm_backlight_register() - Register a backlight device for a
>>> connector
>>> + * @connector: Connector
>>> + *
>>> + * This function registers a backlight device for @connector with the
>>> following
>>> + * characteristics:
>>> + *
>>> + * - The connector sysfs device is used as a parent device for the
>>> backlight device.
>>> + *   Userspace can use this to connect backlight device and connector.
>>> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
>>> + * - Type is "raw"
>>> + * - Scale is "non-linear" (perceptual)
>>> + * - Max brightness is 100 giving a range of 0-100 inclusive
>>> + * - Reading sysfs **brightness** returns the backlight device property
>>> + * - Reading sysfs **actual_brightness** returns the connector state
>>> value
>>> + * - Writing sysfs **bl_power** is ignored, the DPMS connector
>>> property should
>>> + *   be used to control power.
>>> + * - Backlight device suspend/resume events are ignored.
>>> + *
>>> + * Note:
>>> + *
>>> + * Brightness zero should not turn off backlight it should be the
>>> minimum
>>> + * brightness, DPMS handles power.
>>> + *
>>> + * This function must be called from
>>> &drm_connector_funcs->late_register() since
>>> + * it depends on the sysfs device.
>>> + *
>>> + * Returns:
>>> + * Zero on success or negative error code on failure.
>>> + */
>>> +int drm_backlight_register(struct drm_connector *connector)
>>> +{
>>> +    const char *name = NULL;
>>> +    int ret;
>>> +
>>> +    name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
>>> +             connector->dev->primary->index, connector->name);
>>> +    if (!name)
>>> +        return -ENOMEM;
>>> +
>>> +    ret = drm_backlight_register_with_name(connector, name);
>>> +    kfree(name);
>>> +
>>> +    return ret;
>>> +}
>>> +EXPORT_SYMBOL(drm_backlight_register);
>>> +
>>> +/**
>>> + * drm_backlight_unregister() - Unregister backlight device
>>> + * @connector: Connector
>>> + *
>>> + * Unregister a backlight device. This must be called from the
>>> + * &drm_connector_funcs->early_unregister() callback.
>>> + */
>>> +void drm_backlight_unregister(struct drm_connector *connector)
>>> +{
>>> +    backlight_device_unregister(connector->backlight);
>>> +}
>>> +EXPORT_SYMBOL(drm_backlight_unregister);
>>> diff --git a/include/drm/drm_backlight_helper.h
>>> b/include/drm/drm_backlight_helper.h
>>> new file mode 100644
>>> index 000000000000..4151b66eb0b4
>>> --- /dev/null
>>> +++ b/include/drm/drm_backlight_helper.h
>>> @@ -0,0 +1,9 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
>>> +
>>> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
>>> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
>>> +
>>> +int drm_backlight_register(struct drm_connector *connector);
>>> +void drm_backlight_unregister(struct drm_connector *connector);
>>> +
>>> +#endif
>>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>>> index 221910948b37..ce678b694f45 100644
>>> --- a/include/drm/drm_connector.h
>>> +++ b/include/drm/drm_connector.h
>>> @@ -32,6 +32,7 @@
>>>      #include <uapi/drm/drm_mode.h>
>>>    +struct backlight_device;
>>>    struct drm_connector_helper_funcs;
>>>    struct drm_modeset_acquire_ctx;
>>>    struct drm_device;
>>> @@ -656,6 +657,12 @@ struct drm_connector_state {
>>>         */
>>>        u8 max_bpc;
>>>    +    /**
>>> +     * @backlight_brightness: Brightness value of the connector
>>> backlight
>>> +     * device. See drm_backlight_register().
>>> +     */
>>> +    u8 backlight_brightness;
>>> +
>>>        /**
>>>         * @hdr_output_metadata:
>>>         * DRM blob property for HDR output metadata
>>> @@ -1422,6 +1429,9 @@ struct drm_connector {
>>>          /** @hdr_sink_metadata: HDR Metadata Information read from
>>> sink */
>>>        struct hdr_sink_metadata hdr_sink_metadata;
>>> +
>>> +    /** @backlight: Backlight device. See drm_backlight_register() */
>>> +    struct backlight_device *backlight;
>>>    };
>>>      #define obj_to_connector(x) container_of(x, struct drm_connector,
>>> base)
>>>
>>
>>
> 


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

* Re: [PATCH 02/10] drm: Add backlight helper
@ 2020-04-30 15:36         ` Hans de Goede
  0 siblings, 0 replies; 66+ messages in thread
From: Hans de Goede @ 2020-04-30 15:36 UTC (permalink / raw)
  To: Noralf Trønnes, dri-devel, linux-usb
  Cc: Daniel Thompson, Christian Kellner

Hi,

On 4/29/20 8:40 PM, Noralf Trønnes wrote:
> 
> 
> Den 29.04.2020 16.13, skrev Hans de Goede:
>> Hi Noralf,
>>
>> On 4/29/20 2:48 PM, Noralf Trønnes wrote:
>>> This adds a function that creates a backlight device for a connector.
>>> It does not deal with the KMS backlight ABI proposition[1] to add a
>>> connector property. It only takes the current best practise to
>>> standardise
>>> the creation of a backlight device for DRM drivers while we wait for the
>>> property.
>>>
>>> The brightness value is set using a connector state variable and an
>>> atomic
>>> commit.
>>>
>>> I have looked through some of the backlight users and this is what
>>> I've found:
>>>
>>> GNOME [2]
>>> ---------
>>>
>>> Brightness range: 0-100
>>> Scale: Assumes perceptual
>>
>> I'm afraid that this is an incaccurate view of how GNOME handles the
>> brightness. gnome-settings-daemon (g-s-d) exports a DBUS property which has
>> a range of 0 - 100%.  But it also offers step-up and step-down DBUS methods
>> which are used for handling brightness hotkey presses.
>>
>> This is important because g-s-d internally also keeps a step_size variable
>> which depends on the brightness_max value of the sysfs backlight interface,
>> like this:
>>
>> BRIGHTNESS_STEP_AMOUNT(max) ((max) < 20 ? 1 : (max) / 20)
>>
>> This is important because some older laptops where we depend on the
>> vendor specific ACPI method (from e.g. dell-laptop or thinkpad_acpi)
>> there are only 8 levels. So if g-s-d where to simply fake a 1-100
>> range and would leave the stepping up to the DBus API user and that
>> user would want 20 steps, so 5 % per step, then the user would get
>>
>> Start      -> 100% -> level 8
>> Press down ->  95% -> level 7
>> Press down ->  90% -> level 7 *no change*
>> etc.
>>
>> Somewhat related on some embedded ARM devices there are tricks where
>> when the entire scene being rendered does not use 100% white as color,
>> the entire scene has all its rgb values upscaled (too a curve) so that
>> the brightest colors do hit 100% of one of r/g/b, combined with dimming
>> the backlight a bit to save power. As you can imagine for tricks like
>> these you want as much backlight control precision as possible.
>>
>> So any backlight infra we add must expose the true range of the
>> backlight control and not normalize it to a 0-100 range.
>>
>> So sorry, but nack for the current version because of the hardcoding
>> of the range.
> 
> No problem, I just had to start from somewhere, and I started with: Give
> the driver developer as few options as possible, no more than necessary,
> but I didn't really know what was necessary :-)
> 
> The reason I chose a 0-100 range is because the backlight property ABI
> proposal had this range and it maps so nicely to percent. And can the
> ordinary human see brightness changes in more than 100 steps?
> 
> This helper is only to be used by drm drivers and I assumed that all the
> current drivers registering a backlight device could at least do that range.
> 
> Looking through the drivers and their max_brightness values that
> assumption isn't quite right:
> 
> amd: 255
> gma500: 100
> i915: <don't know, register read>
> nouveau/nv40: 31
> nouveau/nv50: 100
> radeon: 255
> shmobile: <don't know, from platform data>
> 
> panel-dsi-cm.c: 255
> panel-jdi-lt070me05000.c: 255
> panel-orisetech-otm8009a.c: 255
> panel-raydium-rm67191.c: 255
> panel-samsung-s6e63m0.c: 10
> panel-sony-acx424akp.c: 1023
> panel-samsung-s6e3ha2.c: 100
> panel-samsung-s6e63j0x03.c: 100
> panel-sony-acx565akm.c: 255
> bridge/parade-ps8622.c: 255
> 
> I'll add max_brightness as an argument together with scale which you
> commented on below.

Ok, sounds good.

>> Also the scale really should be specified by the driver, or be hardcoded
>> to BACKLIGHT_SCALE_UNKNOWN for now. In many cases we do not really know.
>> But for e.g. the acpi_video firmware backlight interface a good guess is
>> that it actually represents a perceptual scale rather then controlling
>> the wattage.
>>
>> Where as the native i915 backlight interface really is controlling
>> the wattage without any perceptual correction.
>>
>> Another problem with your proposal is that it seems to assume that
>> the backlight is controlled by the drm/kms driver. On x86 we have
> 
> Yes, this backlight device is just for drm drivers.

I was hoping you might have some clever ideas how to deal with /
prepare for the backlight driver outside of drm-driver case too.
But I completely understand that you want to limit your scope.

> The reason I spend time on this is because I want to pass backlight
> brightness changes through the atomic machinery like any other state change.

I understand.

Regards,

Hans











>> atleast 3 different drivers for the backlight:
>>
>> 1) The i915 (or amd/nouveau) native driver which more or less
>> directly pokes the PWM controller of the GPU.
>> 2) The ACPI video standard backlight interface
>> 3) Vendor specific ACPI interfaces from older laptops
>>
>> ATM we always register 1. which could remain unchanged with
>> your code and then also register 2/3 if we (the kernel) think
>> that will work better (*) and then rely on userspace prefering
>> these (they have a different backlight_type) over 1.
>>
>> Ideally any infra we add will also offer the option to tie
>> 2. or 3. to the connector...
>>
>> Regards,
>>
>> Hans
>>
>>
>>
>> *) e.g. it will work while the others will not work at all
>>
>>
>>
>>
>>>
>>> Avoids setting the sysfs brightness value to zero if max_brightness >=
>>> 99.
>>> Can connect connector and backlight using the sysfs device.
>>>
>>> KDE [3]
>>> -------
>>>
>>> Brightness range: 0-100
>>> Scale: Assumes perceptual
>>>
>>> Weston [4]
>>> ----------
>>>
>>> Brightness range: 0-255
>>> Scale: Assumes perceptual
>>>
>>> Chromium OS [5]
>>> ---------------
>>>
>>> Brightness range: 0-100
>>> Scale: Depends on the sysfs file 'scale' which is a recent addition
>>> (2019)
>>>
>>> xserver [6]
>>> -----------
>>>
>>> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
>>> Scale: Assumes perceptual
>>>
>>> The builtin modesetting driver[7] does not support Backlight, Intel[8]
>>> does.
>>>
>>> [1]
>>> https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
>>>
>>> [2]
>>> https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
>>>
>>> [3]
>>> https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
>>>
>>> [4]
>>> https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
>>>
>>> [5]
>>> https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
>>>
>>> [6]
>>> https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
>>> [7]
>>> https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
>>>
>>> [8]
>>> https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
>>>
>>>
>>> Cc: Hans de Goede <hdegoede@redhat.com>
>>> Cc: Jani Nikula <jani.nikula@linux.intel.com>
>>> Cc: Martin Peres <martin.peres@linux.intel.com>
>>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>> ---
>>>    Documentation/gpu/drm-kms-helpers.rst  |   6 +
>>>    drivers/gpu/drm/Kconfig                |   7 ++
>>>    drivers/gpu/drm/Makefile               |   1 +
>>>    drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>>>    include/drm/drm_backlight_helper.h     |   9 ++
>>>    include/drm/drm_connector.h            |  10 ++
>>>    6 files changed, 187 insertions(+)
>>>    create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>>>    create mode 100644 include/drm/drm_backlight_helper.h
>>>
>>> diff --git a/Documentation/gpu/drm-kms-helpers.rst
>>> b/Documentation/gpu/drm-kms-helpers.rst
>>> index 9668a7fe2408..29a2f4b49fd2 100644
>>> --- a/Documentation/gpu/drm-kms-helpers.rst
>>> +++ b/Documentation/gpu/drm-kms-helpers.rst
>>> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>>>      .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>>>       :export:
>>> +
>>> +Backlight Helper Reference
>>> +==========================
>>> +
>>> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
>>> +   :export:
>>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>>> index d0aa6cff2e02..f6e13e18c9ca 100644
>>> --- a/drivers/gpu/drm/Kconfig
>>> +++ b/drivers/gpu/drm/Kconfig
>>> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>>>        help
>>>          Choose this if you need the GEM shmem helper functions
>>>    +config DRM_BACKLIGHT_HELPER
>>> +    bool
>>> +    depends on DRM
>>> +    select BACKLIGHT_CLASS_DEVICE
>>> +    help
>>> +      Choose this if you need the backlight device helper functions
>>> +
>>>    config DRM_VM
>>>        bool
>>>        depends on DRM && MMU
>>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>>> index 6493088a0fdd..0d17662dde0a 100644
>>> --- a/drivers/gpu/drm/Makefile
>>> +++ b/drivers/gpu/drm/Makefile
>>> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) +=
>>> drm_fb_helper.o
>>>    drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>>>    drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>>>    drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
>>> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>>>      obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>>>    obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
>>> diff --git a/drivers/gpu/drm/drm_backlight_helper.c
>>> b/drivers/gpu/drm/drm_backlight_helper.c
>>> new file mode 100644
>>> index 000000000000..06e6a75d1d0a
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/drm_backlight_helper.c
>>> @@ -0,0 +1,154 @@
>>> +// SPDX-License-Identifier: GPL-2.0 OR MIT
>>> +/*
>>> + * Copyright 2020 Noralf Trønnes
>>> + */
>>> +
>>> +#include <linux/backlight.h>
>>> +
>>> +#include <drm/drm_atomic.h>
>>> +#include <drm/drm_connector.h>
>>> +#include <drm/drm_drv.h>
>>> +#include <drm/drm_file.h>
>>> +
>>> +static int drm_backlight_update_status(struct backlight_device *bd)
>>> +{
>>> +    struct drm_connector *connector = bl_get_data(bd);
>>> +    struct drm_connector_state *connector_state;
>>> +    struct drm_device *dev = connector->dev;
>>> +    struct drm_modeset_acquire_ctx ctx;
>>> +    struct drm_atomic_state *state;
>>> +    int ret;
>>> +
>>> +    state = drm_atomic_state_alloc(dev);
>>> +    if (!state)
>>> +        return -ENOMEM;
>>> +
>>> +    drm_modeset_acquire_init(&ctx, 0);
>>> +    state->acquire_ctx = &ctx;
>>> +retry:
>>> +    connector_state = drm_atomic_get_connector_state(state, connector);
>>> +    if (IS_ERR(connector_state)) {
>>> +        ret = PTR_ERR(connector_state);
>>> +        goto out;
>>> +    }
>>> +
>>> +    connector_state->backlight_brightness = bd->props.brightness;
>>> +
>>> +    ret = drm_atomic_commit(state);
>>> +out:
>>> +    if (ret == -EDEADLK) {
>>> +        drm_atomic_state_clear(state);
>>> +        drm_modeset_backoff(&ctx);
>>> +        goto retry;
>>> +    }
>>> +
>>> +    drm_atomic_state_put(state);
>>> +
>>> +    drm_modeset_drop_locks(&ctx);
>>> +    drm_modeset_acquire_fini(&ctx);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int drm_backlight_get_brightness(struct backlight_device *bd)
>>> +{
>>> +    struct drm_connector *connector = bl_get_data(bd);
>>> +    struct drm_device *dev = connector->dev;
>>> +    int brightness;
>>> +
>>> +    drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
>>> +    brightness = connector->state->backlight_brightness;
>>> +    drm_modeset_unlock(&dev->mode_config.connection_mutex);
>>> +
>>> +    return brightness;
>>> +}
>>> +
>>> +static const struct backlight_ops drm_backlight_ops = {
>>> +    .get_brightness = drm_backlight_get_brightness,
>>> +    .update_status    = drm_backlight_update_status,
>>> +};
>>> +
>>> +/* Can be exported for drivers carrying a legacy name */
>>> +static int drm_backlight_register_with_name(struct drm_connector
>>> *connector, const char *name)
>>> +{
>>> +    struct backlight_device *bd;
>>> +    const struct backlight_properties props = {
>>> +        .type = BACKLIGHT_RAW,
>>> +        .scale = BACKLIGHT_SCALE_NON_LINEAR,
>>> +        .max_brightness = 100,
>>> +    };
>>> +
>>> +    if (WARN_ON(!drm_core_check_feature(connector->dev,
>>> DRIVER_MODESET) ||
>>> +            !drm_drv_uses_atomic_modeset(connector->dev) ||
>>> +            !connector->kdev))
>>> +        return -EINVAL;
>>> +
>>> +    bd = backlight_device_register(name, connector->kdev, connector,
>>> +                       &drm_backlight_ops, &props);
>>> +    if (IS_ERR(bd))
>>> +        return PTR_ERR(bd);
>>> +
>>> +    connector->backlight = bd;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/**
>>> + * drm_backlight_register() - Register a backlight device for a
>>> connector
>>> + * @connector: Connector
>>> + *
>>> + * This function registers a backlight device for @connector with the
>>> following
>>> + * characteristics:
>>> + *
>>> + * - The connector sysfs device is used as a parent device for the
>>> backlight device.
>>> + *   Userspace can use this to connect backlight device and connector.
>>> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
>>> + * - Type is "raw"
>>> + * - Scale is "non-linear" (perceptual)
>>> + * - Max brightness is 100 giving a range of 0-100 inclusive
>>> + * - Reading sysfs **brightness** returns the backlight device property
>>> + * - Reading sysfs **actual_brightness** returns the connector state
>>> value
>>> + * - Writing sysfs **bl_power** is ignored, the DPMS connector
>>> property should
>>> + *   be used to control power.
>>> + * - Backlight device suspend/resume events are ignored.
>>> + *
>>> + * Note:
>>> + *
>>> + * Brightness zero should not turn off backlight it should be the
>>> minimum
>>> + * brightness, DPMS handles power.
>>> + *
>>> + * This function must be called from
>>> &drm_connector_funcs->late_register() since
>>> + * it depends on the sysfs device.
>>> + *
>>> + * Returns:
>>> + * Zero on success or negative error code on failure.
>>> + */
>>> +int drm_backlight_register(struct drm_connector *connector)
>>> +{
>>> +    const char *name = NULL;
>>> +    int ret;
>>> +
>>> +    name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
>>> +             connector->dev->primary->index, connector->name);
>>> +    if (!name)
>>> +        return -ENOMEM;
>>> +
>>> +    ret = drm_backlight_register_with_name(connector, name);
>>> +    kfree(name);
>>> +
>>> +    return ret;
>>> +}
>>> +EXPORT_SYMBOL(drm_backlight_register);
>>> +
>>> +/**
>>> + * drm_backlight_unregister() - Unregister backlight device
>>> + * @connector: Connector
>>> + *
>>> + * Unregister a backlight device. This must be called from the
>>> + * &drm_connector_funcs->early_unregister() callback.
>>> + */
>>> +void drm_backlight_unregister(struct drm_connector *connector)
>>> +{
>>> +    backlight_device_unregister(connector->backlight);
>>> +}
>>> +EXPORT_SYMBOL(drm_backlight_unregister);
>>> diff --git a/include/drm/drm_backlight_helper.h
>>> b/include/drm/drm_backlight_helper.h
>>> new file mode 100644
>>> index 000000000000..4151b66eb0b4
>>> --- /dev/null
>>> +++ b/include/drm/drm_backlight_helper.h
>>> @@ -0,0 +1,9 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
>>> +
>>> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
>>> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
>>> +
>>> +int drm_backlight_register(struct drm_connector *connector);
>>> +void drm_backlight_unregister(struct drm_connector *connector);
>>> +
>>> +#endif
>>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>>> index 221910948b37..ce678b694f45 100644
>>> --- a/include/drm/drm_connector.h
>>> +++ b/include/drm/drm_connector.h
>>> @@ -32,6 +32,7 @@
>>>      #include <uapi/drm/drm_mode.h>
>>>    +struct backlight_device;
>>>    struct drm_connector_helper_funcs;
>>>    struct drm_modeset_acquire_ctx;
>>>    struct drm_device;
>>> @@ -656,6 +657,12 @@ struct drm_connector_state {
>>>         */
>>>        u8 max_bpc;
>>>    +    /**
>>> +     * @backlight_brightness: Brightness value of the connector
>>> backlight
>>> +     * device. See drm_backlight_register().
>>> +     */
>>> +    u8 backlight_brightness;
>>> +
>>>        /**
>>>         * @hdr_output_metadata:
>>>         * DRM blob property for HDR output metadata
>>> @@ -1422,6 +1429,9 @@ struct drm_connector {
>>>          /** @hdr_sink_metadata: HDR Metadata Information read from
>>> sink */
>>>        struct hdr_sink_metadata hdr_sink_metadata;
>>> +
>>> +    /** @backlight: Backlight device. See drm_backlight_register() */
>>> +    struct backlight_device *backlight;
>>>    };
>>>      #define obj_to_connector(x) container_of(x, struct drm_connector,
>>> base)
>>>
>>
>>
> 

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 00/10] Generic USB Display driver
  2020-04-29 12:48 ` Noralf Trønnes
@ 2020-05-01 13:22   ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-01 13:22 UTC (permalink / raw)
  To: dri-devel, linux-usb



Den 29.04.2020 14.48, skrev Noralf Trønnes:
> Hi,
> 
> A while back I had the idea to turn a Raspberry Pi Zero into a $5
> USB to HDMI/SDTV/DSI/DPI display adapter.
> 
> This series adds a USB host driver and a device/gadget driver to achieve
> that.
> 
> The reason for calling it 'Generic' is so anyone can make a USB
> display/adapter against this driver, all that's needed is to add a USB
> vid:pid. I was hoping to have someone working on a microcontroller based
> USB display by now, but unfortunately that has been delayed. It would
> have been nice to have a microcontroller implementation to ensure that I
> haven't made things unnecessary difficult to implement.
> 
> Performance
> The one thing that decides how useful this all is, is how smooth an
> experience it gives. My hope was that it should not be noticeably laggy
> with ordinary office use on 1920x1080@RG16. I'm pleased to see that it's
> also possible to watch youtube movies, although not in fullscreen.
> 
> Some of the main factors that affects performance:
> - Display resolution
> - Userspace providing damage reports (FB_DAMAGE_CLIPS or
> DRM_IOCTL_MODE_DIRTYFB)
> - Color depth (DRM_CAP_DUMB_PREFERRED_DEPTH = 16 if RGB565)
> - How well the frames compress (lz4)
> - Gadget device memory bandwidth, CPU power for decompression
> - (Big endian hosts will have to do byte swapping on the frames)

One factor that I forgot is USB2 vs USB3.
The Pi's have a USB2 Device controller (dwc2). I couldn't find a cheap
board with a USB3 Device controller that could run mainline Linux, so I
haven't tried that.

> 
> I've tested these:
> - xorg-server on Pi4. This was nice and smooth since it uses
> DRM_IOCTL_MODE_DIRTYFB and honours DRM_CAP_DUMB_PREFERRED_DEPTH.
> - Ubuntu 20.04 GNOME on x86. This was useable, but not so good for
> movies. GNOME doesn't look at DRM_CAP_DUMB_PREFERRED_DEPTH and doesn't
> set FB_DAMAGE_CLIPS on the pageflips.
> 
> I've made a short video to show what it looks like[1].

I got a question if this would work with usbip[4], USB over IP.
I did a quick test with two Pi4's connected by cable to the same network
switch (1Gb). Showing a movie in a window like my previous test didn't
show much of a difference. Maybe some occasional glitching, hard to tell
without proper tests.

There's no pageflipping on the device side, so it could be tearing that
I saw.

Noralf.

[4] tools/usb/usbip/README

> 
> I have used a Pi4 as the gadget during development since it has much
> better memory bandwith (4000 vs 200 MBps)[2] and CPU than the PiZ. They
> both have the same gadget controller (dwc2).
> 
> I did an RFC [3] of this 2 months ago where I looked at doing a Multi
> Function USB device. I gave up on that when I realised how much work the
> review process would be. So I stripped down to my original idea. I have
> made sure that the drivers will tolerate another USB interface of type
> VENDOR, so it's still possible for the display to be part of a multi
> function device.
> 
> Noralf.
> 
> [1] https://youtu.be/AhGZWwUm8JU
> [2] https://magpi.raspberrypi.org/articles/raspberry-pi-specs-benchmarks
> [3] https://patchwork.freedesktop.org/series/73508/
> 

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

* Re: [PATCH 00/10] Generic USB Display driver
@ 2020-05-01 13:22   ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-01 13:22 UTC (permalink / raw)
  To: dri-devel, linux-usb



Den 29.04.2020 14.48, skrev Noralf Trønnes:
> Hi,
> 
> A while back I had the idea to turn a Raspberry Pi Zero into a $5
> USB to HDMI/SDTV/DSI/DPI display adapter.
> 
> This series adds a USB host driver and a device/gadget driver to achieve
> that.
> 
> The reason for calling it 'Generic' is so anyone can make a USB
> display/adapter against this driver, all that's needed is to add a USB
> vid:pid. I was hoping to have someone working on a microcontroller based
> USB display by now, but unfortunately that has been delayed. It would
> have been nice to have a microcontroller implementation to ensure that I
> haven't made things unnecessary difficult to implement.
> 
> Performance
> The one thing that decides how useful this all is, is how smooth an
> experience it gives. My hope was that it should not be noticeably laggy
> with ordinary office use on 1920x1080@RG16. I'm pleased to see that it's
> also possible to watch youtube movies, although not in fullscreen.
> 
> Some of the main factors that affects performance:
> - Display resolution
> - Userspace providing damage reports (FB_DAMAGE_CLIPS or
> DRM_IOCTL_MODE_DIRTYFB)
> - Color depth (DRM_CAP_DUMB_PREFERRED_DEPTH = 16 if RGB565)
> - How well the frames compress (lz4)
> - Gadget device memory bandwidth, CPU power for decompression
> - (Big endian hosts will have to do byte swapping on the frames)

One factor that I forgot is USB2 vs USB3.
The Pi's have a USB2 Device controller (dwc2). I couldn't find a cheap
board with a USB3 Device controller that could run mainline Linux, so I
haven't tried that.

> 
> I've tested these:
> - xorg-server on Pi4. This was nice and smooth since it uses
> DRM_IOCTL_MODE_DIRTYFB and honours DRM_CAP_DUMB_PREFERRED_DEPTH.
> - Ubuntu 20.04 GNOME on x86. This was useable, but not so good for
> movies. GNOME doesn't look at DRM_CAP_DUMB_PREFERRED_DEPTH and doesn't
> set FB_DAMAGE_CLIPS on the pageflips.
> 
> I've made a short video to show what it looks like[1].

I got a question if this would work with usbip[4], USB over IP.
I did a quick test with two Pi4's connected by cable to the same network
switch (1Gb). Showing a movie in a window like my previous test didn't
show much of a difference. Maybe some occasional glitching, hard to tell
without proper tests.

There's no pageflipping on the device side, so it could be tearing that
I saw.

Noralf.

[4] tools/usb/usbip/README

> 
> I have used a Pi4 as the gadget during development since it has much
> better memory bandwith (4000 vs 200 MBps)[2] and CPU than the PiZ. They
> both have the same gadget controller (dwc2).
> 
> I did an RFC [3] of this 2 months ago where I looked at doing a Multi
> Function USB device. I gave up on that when I realised how much work the
> review process would be. So I stripped down to my original idea. I have
> made sure that the drivers will tolerate another USB interface of type
> VENDOR, so it's still possible for the display to be part of a multi
> function device.
> 
> Noralf.
> 
> [1] https://youtu.be/AhGZWwUm8JU
> [2] https://magpi.raspberrypi.org/articles/raspberry-pi-specs-benchmarks
> [3] https://patchwork.freedesktop.org/series/73508/
> 
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 08/10] drm: Add Generic USB Display driver
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-05-02 17:58     ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-02 17:58 UTC (permalink / raw)
  To: dri-devel, linux-usb



Den 29.04.2020 14.48, skrev Noralf Trønnes:
> This adds a generic USB display driver with the intention that it can be
> used with future USB interfaced low end displays/adapters. The Linux
> gadget device driver will serve as the canonical device implementation.
> 
> The following DRM properties are supported:
> - Plane rotation
> - Connector TV properties
> 
> There is also support for backlight brightness exposed as a backlight
> device.
> 
> Display modes can be made available to the host driver either as DRM
> display modes or through EDID. If both are present, EDID is just passed
> on to userspace.
> 
> Performance is preferred over color depth, so if the device supports
> RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16.
> 
> If the device transfer buffer can't fit an uncompressed framebuffer
> update, the update is split up into parts that do fit.
> 
> Optimal user experience is achieved by providing damage reports either by
> setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB.
> 
> LZ4 compression is used if the device supports it.
> 
> The driver supports a one bit monochrome transfer format: R1. This is not
> implemented in the gadget driver. It is added in preparation for future
> monochrome e-ink displays.
> 
> The driver is MIT licensed to smooth the path for any BSD port of the
> driver.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---

> diff --git a/drivers/gpu/drm/gud/gud_drm_drv.c b/drivers/gpu/drm/gud/gud_drm_drv.c

> +static int gud_drm_probe(struct usb_interface *interface,
> +			 const struct usb_device_id *id)
> +{

<snip>

> +	gdrm = kzalloc(sizeof(*gdrm), GFP_KERNEL);
> +	if (!gdrm)
> +		return -ENOMEM;
> +
> +	gdrm->usb = usb;
> +	gdrm->ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
> +	gdrm->compression = desc.bCompression & GUD_DRM_COMPRESSION_LZ4;
> +
> +	drm = &gdrm->drm;
> +	ret = devm_drm_dev_init(dev, drm, &gud_drm_driver);
> +	if (ret) {
> +		kfree(gdrm);
> +		return ret;
> +	}

I see that Daniel's series has landed now, so I will use
devm_drm_dev_alloc() and drmm_mode_config_init() in the next version.

<snip>

> +	drm_fbdev_generic_setup(drm, 16);

This should be:

	drm_fbdev_generic_setup(drm, 0);

Noralf.

> +
> +	return 0;
> +}

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

* Re: [PATCH 08/10] drm: Add Generic USB Display driver
@ 2020-05-02 17:58     ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-02 17:58 UTC (permalink / raw)
  To: dri-devel, linux-usb



Den 29.04.2020 14.48, skrev Noralf Trønnes:
> This adds a generic USB display driver with the intention that it can be
> used with future USB interfaced low end displays/adapters. The Linux
> gadget device driver will serve as the canonical device implementation.
> 
> The following DRM properties are supported:
> - Plane rotation
> - Connector TV properties
> 
> There is also support for backlight brightness exposed as a backlight
> device.
> 
> Display modes can be made available to the host driver either as DRM
> display modes or through EDID. If both are present, EDID is just passed
> on to userspace.
> 
> Performance is preferred over color depth, so if the device supports
> RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16.
> 
> If the device transfer buffer can't fit an uncompressed framebuffer
> update, the update is split up into parts that do fit.
> 
> Optimal user experience is achieved by providing damage reports either by
> setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB.
> 
> LZ4 compression is used if the device supports it.
> 
> The driver supports a one bit monochrome transfer format: R1. This is not
> implemented in the gadget driver. It is added in preparation for future
> monochrome e-ink displays.
> 
> The driver is MIT licensed to smooth the path for any BSD port of the
> driver.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---

> diff --git a/drivers/gpu/drm/gud/gud_drm_drv.c b/drivers/gpu/drm/gud/gud_drm_drv.c

> +static int gud_drm_probe(struct usb_interface *interface,
> +			 const struct usb_device_id *id)
> +{

<snip>

> +	gdrm = kzalloc(sizeof(*gdrm), GFP_KERNEL);
> +	if (!gdrm)
> +		return -ENOMEM;
> +
> +	gdrm->usb = usb;
> +	gdrm->ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
> +	gdrm->compression = desc.bCompression & GUD_DRM_COMPRESSION_LZ4;
> +
> +	drm = &gdrm->drm;
> +	ret = devm_drm_dev_init(dev, drm, &gud_drm_driver);
> +	if (ret) {
> +		kfree(gdrm);
> +		return ret;
> +	}

I see that Daniel's series has landed now, so I will use
devm_drm_dev_alloc() and drmm_mode_config_init() in the next version.

<snip>

> +	drm_fbdev_generic_setup(drm, 16);

This should be:

	drm_fbdev_generic_setup(drm, 0);

Noralf.

> +
> +	return 0;
> +}
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-05-03  7:13     ` Sam Ravnborg
  -1 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  7:13 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: dri-devel, linux-usb, Jingoo Han, Daniel Thompson, Lee Jones

Hi Noralf.

On Wed, Apr 29, 2020 at 02:48:21PM +0200, Noralf Trønnes wrote:
> Add a way to lookup a backlight device based on its name.
> Will be used by a USB display gadget getting the name from configfs.
> 
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Cc: Jingoo Han <jingoohan1@gmail.com>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Simple and well-documented.
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> ---
>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>  include/linux/backlight.h           |  1 +
>  2 files changed, 22 insertions(+)
> 
> diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
> index cac3e35d7630..92d80aa0c0ef 100644
> --- a/drivers/video/backlight/backlight.c
> +++ b/drivers/video/backlight/backlight.c
> @@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
>  }
>  EXPORT_SYMBOL(backlight_device_get_by_type);
>  
> +/**
> + * backlight_device_get_by_name - Get backlight device by name
> + * @name: Device name
> + *
> + * This function looks up a backlight device by its name. It obtains a reference
> + * on the backlight device and it is the caller's responsibility to drop the
> + * reference by calling backlight_put().
> + *
> + * Returns:
> + * A pointer to the backlight device if found, otherwise NULL.
> + */
> +struct backlight_device *backlight_device_get_by_name(const char *name)
> +{
> +	struct device *dev;
> +
> +	dev = class_find_device_by_name(backlight_class, name);
> +
> +	return dev ? to_backlight_device(dev) : NULL;
> +}
> +EXPORT_SYMBOL(backlight_device_get_by_name);
> +
>  /**
>   * backlight_device_unregister - unregisters a backlight device object.
>   * @bd: the backlight device object to be unregistered and freed.
> diff --git a/include/linux/backlight.h b/include/linux/backlight.h
> index c7d6b2e8c3b5..56e4580d4f55 100644
> --- a/include/linux/backlight.h
> +++ b/include/linux/backlight.h
> @@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
>  extern int backlight_register_notifier(struct notifier_block *nb);
>  extern int backlight_unregister_notifier(struct notifier_block *nb);
>  extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
> +struct backlight_device *backlight_device_get_by_name(const char *name);
>  extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
>  
>  #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-05-03  7:13     ` Sam Ravnborg
  0 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  7:13 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: Jingoo Han, Daniel Thompson, linux-usb, Lee Jones, dri-devel

Hi Noralf.

On Wed, Apr 29, 2020 at 02:48:21PM +0200, Noralf Trønnes wrote:
> Add a way to lookup a backlight device based on its name.
> Will be used by a USB display gadget getting the name from configfs.
> 
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Cc: Jingoo Han <jingoohan1@gmail.com>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Simple and well-documented.
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> ---
>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
>  include/linux/backlight.h           |  1 +
>  2 files changed, 22 insertions(+)
> 
> diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
> index cac3e35d7630..92d80aa0c0ef 100644
> --- a/drivers/video/backlight/backlight.c
> +++ b/drivers/video/backlight/backlight.c
> @@ -432,6 +432,27 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
>  }
>  EXPORT_SYMBOL(backlight_device_get_by_type);
>  
> +/**
> + * backlight_device_get_by_name - Get backlight device by name
> + * @name: Device name
> + *
> + * This function looks up a backlight device by its name. It obtains a reference
> + * on the backlight device and it is the caller's responsibility to drop the
> + * reference by calling backlight_put().
> + *
> + * Returns:
> + * A pointer to the backlight device if found, otherwise NULL.
> + */
> +struct backlight_device *backlight_device_get_by_name(const char *name)
> +{
> +	struct device *dev;
> +
> +	dev = class_find_device_by_name(backlight_class, name);
> +
> +	return dev ? to_backlight_device(dev) : NULL;
> +}
> +EXPORT_SYMBOL(backlight_device_get_by_name);
> +
>  /**
>   * backlight_device_unregister - unregisters a backlight device object.
>   * @bd: the backlight device object to be unregistered and freed.
> diff --git a/include/linux/backlight.h b/include/linux/backlight.h
> index c7d6b2e8c3b5..56e4580d4f55 100644
> --- a/include/linux/backlight.h
> +++ b/include/linux/backlight.h
> @@ -190,6 +190,7 @@ extern void backlight_force_update(struct backlight_device *bd,
>  extern int backlight_register_notifier(struct notifier_block *nb);
>  extern int backlight_unregister_notifier(struct notifier_block *nb);
>  extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type);
> +struct backlight_device *backlight_device_get_by_name(const char *name);
>  extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness);
>  
>  #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 04/10] drm/client: Add drm_client_framebuffer_flush()
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-05-03  7:48     ` Sam Ravnborg
  -1 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  7:48 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-usb

Hi Noralf.

On Wed, Apr 29, 2020 at 02:48:24PM +0200, Noralf Trønnes wrote:
> Some drivers need explicit flushing of buffer changes, add a function
> that does that.
I trust you on this.

> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Some bikeshedding below. Either way:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>

> ---
>  drivers/gpu/drm/drm_client.c | 31 +++++++++++++++++++++++++++++++
>  include/drm/drm_client.h     |  1 +
>  2 files changed, 32 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
> index cb5ee9f1ffaa..8dbc2ecdcaea 100644
> --- a/drivers/gpu/drm/drm_client.c
> +++ b/drivers/gpu/drm/drm_client.c
> @@ -483,6 +483,37 @@ void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
>  }
>  EXPORT_SYMBOL(drm_client_framebuffer_delete);
>  
> +/**
> + * drm_client_framebuffer_flush - Manually flush client framebuffer
> + * @buffer: DRM client buffer (can be NULL)
> + * @rect: Damage rectangle (if NULL flushes all)
> + *
> + * This calls &drm_framebuffer_funcs->dirty (if present) to flush buffer changes
> + * for drivers that need it.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */

Alternative proposal - that I think is simpler.
But both variants works for me.
> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect)
> +{
	struct drm_framebuffer_funcs *funcs;
	struct drm_clip_rect clip;
> +
> +	if (!buffer || !buffer->fb || !buffer->fb->funcs->dirty)
> +		return 0;
	funcs = buffer->fb->funcs;
> +
> +	if (rect) {
> +		clip.x1 = rect->x1;
> +		clip.y1 = rect->y1;
> +		clip.x2 = rect->x2;
> +		clip.y2 = rect->y2;
		return funcs->dirty(buffer->fb, buffer->client->file,
				    0, 0, &clip, 1);
> +	} else {
		return funcs->dirty(buffer->fb, buffer->client->file,
				    0, 0, NULL, 0);
	}

> +
> +	return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
> +					0, 0, clip, clip ? 1 : 0);
> +}
> +EXPORT_SYMBOL(drm_client_framebuffer_flush);
> +
>  #ifdef CONFIG_DEBUG_FS
>  static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
>  {
> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> index bbb5689fa9a8..6ef5364d6dfb 100644
> --- a/include/drm/drm_client.h
> +++ b/include/drm/drm_client.h
> @@ -156,6 +156,7 @@ struct drm_client_buffer {
>  struct drm_client_buffer *
>  drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format);
>  void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect);
>  void *drm_client_buffer_vmap(struct drm_client_buffer *buffer);
>  void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>  
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 04/10] drm/client: Add drm_client_framebuffer_flush()
@ 2020-05-03  7:48     ` Sam Ravnborg
  0 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  7:48 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: linux-usb, dri-devel

Hi Noralf.

On Wed, Apr 29, 2020 at 02:48:24PM +0200, Noralf Trønnes wrote:
> Some drivers need explicit flushing of buffer changes, add a function
> that does that.
I trust you on this.

> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Some bikeshedding below. Either way:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>

> ---
>  drivers/gpu/drm/drm_client.c | 31 +++++++++++++++++++++++++++++++
>  include/drm/drm_client.h     |  1 +
>  2 files changed, 32 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
> index cb5ee9f1ffaa..8dbc2ecdcaea 100644
> --- a/drivers/gpu/drm/drm_client.c
> +++ b/drivers/gpu/drm/drm_client.c
> @@ -483,6 +483,37 @@ void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
>  }
>  EXPORT_SYMBOL(drm_client_framebuffer_delete);
>  
> +/**
> + * drm_client_framebuffer_flush - Manually flush client framebuffer
> + * @buffer: DRM client buffer (can be NULL)
> + * @rect: Damage rectangle (if NULL flushes all)
> + *
> + * This calls &drm_framebuffer_funcs->dirty (if present) to flush buffer changes
> + * for drivers that need it.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */

Alternative proposal - that I think is simpler.
But both variants works for me.
> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect)
> +{
	struct drm_framebuffer_funcs *funcs;
	struct drm_clip_rect clip;
> +
> +	if (!buffer || !buffer->fb || !buffer->fb->funcs->dirty)
> +		return 0;
	funcs = buffer->fb->funcs;
> +
> +	if (rect) {
> +		clip.x1 = rect->x1;
> +		clip.y1 = rect->y1;
> +		clip.x2 = rect->x2;
> +		clip.y2 = rect->y2;
		return funcs->dirty(buffer->fb, buffer->client->file,
				    0, 0, &clip, 1);
> +	} else {
		return funcs->dirty(buffer->fb, buffer->client->file,
				    0, 0, NULL, 0);
	}

> +
> +	return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
> +					0, 0, clip, clip ? 1 : 0);
> +}
> +EXPORT_SYMBOL(drm_client_framebuffer_flush);
> +
>  #ifdef CONFIG_DEBUG_FS
>  static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
>  {
> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> index bbb5689fa9a8..6ef5364d6dfb 100644
> --- a/include/drm/drm_client.h
> +++ b/include/drm/drm_client.h
> @@ -156,6 +156,7 @@ struct drm_client_buffer {
>  struct drm_client_buffer *
>  drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format);
>  void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect);
>  void *drm_client_buffer_vmap(struct drm_client_buffer *buffer);
>  void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>  
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 05/10] drm/client: Add drm_client_modeset_check()
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-05-03  8:03     ` Sam Ravnborg
  -1 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  8:03 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-usb

Hi Noralf.

On Wed, Apr 29, 2020 at 02:48:25PM +0200, Noralf Trønnes wrote:
> Add a way for client to check the configuration before comitting.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Two small ntis. With these addressed:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> ---
>  drivers/gpu/drm/drm_client_modeset.c | 35 ++++++++++++++++++++++++----
>  include/drm/drm_client.h             |  1 +
>  2 files changed, 32 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
> index 7443114bd713..177158ff2a40 100644
> --- a/drivers/gpu/drm/drm_client_modeset.c
> +++ b/drivers/gpu/drm/drm_client_modeset.c
> @@ -966,7 +966,7 @@ bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
>  }
>  EXPORT_SYMBOL(drm_client_rotation);
>  
> -static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active)
> +static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
>  {
>  	struct drm_device *dev = client->dev;
>  	struct drm_plane *plane;
> @@ -1033,7 +1033,10 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>  		}
>  	}
>  
> -	ret = drm_atomic_commit(state);
> +	if (check)
> +		ret = drm_atomic_check_only(state);
> +	else
> +		ret = drm_atomic_commit(state);
>  
>  out_state:
>  	if (ret == -EDEADLK)
> @@ -1094,6 +1097,30 @@ static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
>  	return ret;
>  }
>  
> +/**
> + * drm_client_modeset_check() - Check CRTC configuration
This part of the comment does not match the description below.


> + * @client: DRM client
> + *
> + * Check modeset configuration.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_check(struct drm_client_dev *client)
> +{
> +	int ret;
> +
> +	if (!drm_drv_uses_atomic_modeset(client->dev))
> +		return 0;
If client does not use atomic the check should fail - no?

> +
> +	mutex_lock(&client->modeset_mutex);
> +	ret = drm_client_modeset_commit_atomic(client, true, true);
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_modeset_check);
> +
>  /**
>   * drm_client_modeset_commit_locked() - Force commit CRTC configuration
>   * @client: DRM client
> @@ -1112,7 +1139,7 @@ int drm_client_modeset_commit_locked(struct drm_client_dev *client)
>  
>  	mutex_lock(&client->modeset_mutex);
>  	if (drm_drv_uses_atomic_modeset(dev))
> -		ret = drm_client_modeset_commit_atomic(client, true);
> +		ret = drm_client_modeset_commit_atomic(client, true, false);
>  	else
>  		ret = drm_client_modeset_commit_legacy(client);
>  	mutex_unlock(&client->modeset_mutex);
> @@ -1188,7 +1215,7 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
>  
>  	mutex_lock(&client->modeset_mutex);
>  	if (drm_drv_uses_atomic_modeset(dev))
> -		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON);
> +		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
>  	else
>  		drm_client_modeset_dpms_legacy(client, mode);
>  	mutex_unlock(&client->modeset_mutex);
> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> index 6ef5364d6dfb..b6ffa4863e45 100644
> --- a/include/drm/drm_client.h
> +++ b/include/drm/drm_client.h
> @@ -164,6 +164,7 @@ int drm_client_modeset_create(struct drm_client_dev *client);
>  void drm_client_modeset_free(struct drm_client_dev *client);
>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
> +int drm_client_modeset_check(struct drm_client_dev *client);
>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
>  int drm_client_modeset_commit(struct drm_client_dev *client);
>  int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 05/10] drm/client: Add drm_client_modeset_check()
@ 2020-05-03  8:03     ` Sam Ravnborg
  0 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  8:03 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: linux-usb, dri-devel

Hi Noralf.

On Wed, Apr 29, 2020 at 02:48:25PM +0200, Noralf Trønnes wrote:
> Add a way for client to check the configuration before comitting.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Two small ntis. With these addressed:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> ---
>  drivers/gpu/drm/drm_client_modeset.c | 35 ++++++++++++++++++++++++----
>  include/drm/drm_client.h             |  1 +
>  2 files changed, 32 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
> index 7443114bd713..177158ff2a40 100644
> --- a/drivers/gpu/drm/drm_client_modeset.c
> +++ b/drivers/gpu/drm/drm_client_modeset.c
> @@ -966,7 +966,7 @@ bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
>  }
>  EXPORT_SYMBOL(drm_client_rotation);
>  
> -static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active)
> +static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
>  {
>  	struct drm_device *dev = client->dev;
>  	struct drm_plane *plane;
> @@ -1033,7 +1033,10 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>  		}
>  	}
>  
> -	ret = drm_atomic_commit(state);
> +	if (check)
> +		ret = drm_atomic_check_only(state);
> +	else
> +		ret = drm_atomic_commit(state);
>  
>  out_state:
>  	if (ret == -EDEADLK)
> @@ -1094,6 +1097,30 @@ static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
>  	return ret;
>  }
>  
> +/**
> + * drm_client_modeset_check() - Check CRTC configuration
This part of the comment does not match the description below.


> + * @client: DRM client
> + *
> + * Check modeset configuration.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_check(struct drm_client_dev *client)
> +{
> +	int ret;
> +
> +	if (!drm_drv_uses_atomic_modeset(client->dev))
> +		return 0;
If client does not use atomic the check should fail - no?

> +
> +	mutex_lock(&client->modeset_mutex);
> +	ret = drm_client_modeset_commit_atomic(client, true, true);
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_modeset_check);
> +
>  /**
>   * drm_client_modeset_commit_locked() - Force commit CRTC configuration
>   * @client: DRM client
> @@ -1112,7 +1139,7 @@ int drm_client_modeset_commit_locked(struct drm_client_dev *client)
>  
>  	mutex_lock(&client->modeset_mutex);
>  	if (drm_drv_uses_atomic_modeset(dev))
> -		ret = drm_client_modeset_commit_atomic(client, true);
> +		ret = drm_client_modeset_commit_atomic(client, true, false);
>  	else
>  		ret = drm_client_modeset_commit_legacy(client);
>  	mutex_unlock(&client->modeset_mutex);
> @@ -1188,7 +1215,7 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
>  
>  	mutex_lock(&client->modeset_mutex);
>  	if (drm_drv_uses_atomic_modeset(dev))
> -		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON);
> +		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
>  	else
>  		drm_client_modeset_dpms_legacy(client, mode);
>  	mutex_unlock(&client->modeset_mutex);
> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> index 6ef5364d6dfb..b6ffa4863e45 100644
> --- a/include/drm/drm_client.h
> +++ b/include/drm/drm_client.h
> @@ -164,6 +164,7 @@ int drm_client_modeset_create(struct drm_client_dev *client);
>  void drm_client_modeset_free(struct drm_client_dev *client);
>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
> +int drm_client_modeset_check(struct drm_client_dev *client);
>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
>  int drm_client_modeset_commit(struct drm_client_dev *client);
>  int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 06/10] drm/client: Add a way to set modeset, properties and rotation
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-05-03  8:47     ` Sam Ravnborg
  -1 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  8:47 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-usb

Hi Noralf.

Some comments in the following - partly because I still do not fully
grasp modeset etc.

	Sam

On Wed, Apr 29, 2020 at 02:48:26PM +0200, Noralf Trønnes wrote:
> This adds functions for clients that need more control over the
> configuration than what's setup by drm_client_modeset_probe().
> Connector, fb and display mode can be set using drm_client_modeset_set().
> Plane rotation can be set using drm_client_modeset_set_rotation() and
> other properties using drm_client_modeset_set_property(). Property
> setting is only implemented for atomic drivers.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/gpu/drm/drm_client_modeset.c | 139 +++++++++++++++++++++++++++
>  include/drm/drm_client.h             |  38 +++++++-
>  2 files changed, 176 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
> index 177158ff2a40..1eef6869cae1 100644
> --- a/drivers/gpu/drm/drm_client_modeset.c
> +++ b/drivers/gpu/drm/drm_client_modeset.c
> @@ -83,6 +83,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client)
>  		}
>  		modeset->num_connectors = 0;
>  	}
> +
> +	kfree(client->properties);
> +	client->properties = NULL;
> +	client->num_properties = 0;

I failed to see why this code is in drm_client_modeset_release()
and not in drm_client_modeset_free().
In other words - why do we need to free properties in drm_client_modeset_probe()
which is the only other user of drm_client_modeset_release().

>  }
>  
>  void drm_client_modeset_free(struct drm_client_dev *client)
> @@ -879,6 +883,132 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>  }
>  EXPORT_SYMBOL(drm_client_modeset_probe);
>  
> +/**
> + * drm_client_modeset_set() - Set modeset configuration
> + * @client: DRM client
> + * @connector: Connector
> + * @mode: Display mode
> + * @fb: Framebuffer
> + *
> + * This function releases any current modeset info, including properties, and
> + * sets the new modeset in the client's modeset array.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb)
> +{
> +	struct drm_mode_set *modeset;
> +	int ret = -ENOENT;
> +
Need to check if atomic is supported?
Or maybe I just do not get all this atomic stuff yet..

> +	mutex_lock(&client->modeset_mutex);
> +
> +	drm_client_modeset_release(client);
If the check below fails - is it then correct to release modeset?
> +
> +	if (!connector || !mode || !fb) {
> +		ret = 0;
Error out, it is not a success if we cannot do anything?

> +		goto unlock;
> +	}
> +
> +	drm_client_for_each_modeset(modeset, client) {
> +		if (!connector_has_possible_crtc(connector, modeset->crtc))
> +			continue;
> +
> +		modeset->mode = drm_mode_duplicate(client->dev, mode);
> +		if (!modeset->mode) {
> +			ret = -ENOMEM;
> +			break;
> +		}
> +
> +		drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V);
> +
> +		drm_connector_get(connector);
> +		modeset->connectors[modeset->num_connectors++] = connector;
> +
> +		modeset->fb = fb;
> +		ret = 0;
> +		break;
> +	}
> +unlock:
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_modeset_set);
> +
> +/**
> + * drm_client_modeset_set_property() - Set a property on the current configuration
> + * @client: DRM client
> + * @obj: DRM Mode Object
> + * @prop: DRM Property
> + * @value: Property value
> + *
> + * Note: Currently only implemented for atomic drivers.
Are there any reason to in the future support legacy (non-atomic)
drivers.
If not then reword - as the above reads like it is on a TODO list to
support legacy drivers.

> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
> +				    struct drm_property *prop, u64 value)
> +{
> +	struct drm_client_property *properties;
> +	int ret = 0;
> +
> +	if (!prop)
> +		return -EINVAL;
> +
Need to check if atomic is supported?
Or maybe I just do not get all this atomic stuff yet..

> +	mutex_lock(&client->modeset_mutex);
> +
> +	properties = krealloc(client->properties,
> +			      (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL);
> +	if (!properties) {
> +		ret = -ENOMEM;
> +		goto unlock;
> +	}
> +
> +	properties[client->num_properties].obj = obj;
> +	properties[client->num_properties].prop = prop;
The drm_client_modeset_set_property() take over ownership of prop.
This looks wrong - should this be a copy of prop?
properties[].prop should not be a pointer but a full drm_property
struct?

> +	properties[client->num_properties].value = value;
> +	client->properties = properties;
> +	client->num_properties++;
> +unlock:
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_modeset_set_property);
> +
> +/**
> + * drm_client_modeset_set_rotation() - Set rotation on the current configuration
> + * @client: DRM client
> + * @value: Rotation value
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value)
> +{
> +	struct drm_plane *plane = NULL;
> +	struct drm_mode_set *modeset;
> +
> +	mutex_lock(&client->modeset_mutex);
> +	drm_client_for_each_modeset(modeset, client) {
> +		if (modeset->mode) {
> +			plane = modeset->crtc->primary;
> +			break;
> +		}
> +	}
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	if (!plane)
> +		return -ENOENT;
> +
> +	return drm_client_modeset_set_property(client, &plane->base,
> +					       plane->rotation_property, value);
> +}
> +EXPORT_SYMBOL(drm_client_modeset_set_rotation);
> +
>  /**
>   * drm_client_rotation() - Check the initial rotation value
>   * @modeset: DRM modeset
> @@ -973,6 +1103,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>  	struct drm_atomic_state *state;
>  	struct drm_modeset_acquire_ctx ctx;
>  	struct drm_mode_set *mode_set;
> +	unsigned int i;
>  	int ret;
>  
>  	drm_modeset_acquire_init(&ctx, 0);
> @@ -1033,6 +1164,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>  		}
>  	}
>  
> +	for (i = 0; i < client->num_properties; i++) {
> +		struct drm_client_property *prop = &client->properties[i];
> +
> +		ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value);
> +		if (ret)
> +			goto out_state;
> +	}
> +
With the code above drm_atomic_set_property() is called also when check
is true. I had expected that check would not change anything.

>  	if (check)
>  		ret = drm_atomic_check_only(state);
>  	else
> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> index b6ffa4863e45..4b266741ec0e 100644
> --- a/include/drm/drm_client.h
> +++ b/include/drm/drm_client.h
> @@ -16,6 +16,7 @@ struct drm_file;
>  struct drm_framebuffer;
>  struct drm_gem_object;
>  struct drm_minor;
> +struct drm_property;
>  struct module;
>  
>  /**
> @@ -64,6 +65,26 @@ struct drm_client_funcs {
>  	int (*hotplug)(struct drm_client_dev *client);
>  };
>  
> +/**
> + * struct drm_client_property - DRM client property
> + */
> +struct drm_client_property {
> +	/**
> +	 * @obj: DRM Mode Object to which the property belongs.
> +	 */
> +	struct drm_mode_object *obj;
> +
> +	/**
> +	 * @prop: DRM Property.
> +	 */
> +	struct drm_property *prop;
> +
> +	/**
> +	 * @value: Property value.
> +	 */
> +	u64 value;
> +};
> +
>  /**
>   * struct drm_client_dev - DRM client instance
>   */
> @@ -97,7 +118,7 @@ struct drm_client_dev {
>  	struct drm_file *file;
>  
>  	/**
> -	 * @modeset_mutex: Protects @modesets.
> +	 * @modeset_mutex: Protects @modesets and @properties.
>  	 */
>  	struct mutex modeset_mutex;
>  
> @@ -105,6 +126,16 @@ struct drm_client_dev {
>  	 * @modesets: CRTC configurations
>  	 */
>  	struct drm_mode_set *modesets;
> +
> +	/**
> +	 * @properties: DRM properties attached to the configuration.
> +	 */
> +	struct drm_client_property *properties;
> +
> +	/**
> +	 * @num_properties: Number of attached properties.
> +	 */
> +	unsigned int num_properties;
>  };
>  
>  int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
> @@ -163,6 +194,11 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>  int drm_client_modeset_create(struct drm_client_dev *client);
>  void drm_client_modeset_free(struct drm_client_dev *client);
>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb);
> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
> +				    struct drm_property *prop, u64 value);
> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value);
>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
>  int drm_client_modeset_check(struct drm_client_dev *client);
>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 06/10] drm/client: Add a way to set modeset, properties and rotation
@ 2020-05-03  8:47     ` Sam Ravnborg
  0 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03  8:47 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: linux-usb, dri-devel

Hi Noralf.

Some comments in the following - partly because I still do not fully
grasp modeset etc.

	Sam

On Wed, Apr 29, 2020 at 02:48:26PM +0200, Noralf Trønnes wrote:
> This adds functions for clients that need more control over the
> configuration than what's setup by drm_client_modeset_probe().
> Connector, fb and display mode can be set using drm_client_modeset_set().
> Plane rotation can be set using drm_client_modeset_set_rotation() and
> other properties using drm_client_modeset_set_property(). Property
> setting is only implemented for atomic drivers.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/gpu/drm/drm_client_modeset.c | 139 +++++++++++++++++++++++++++
>  include/drm/drm_client.h             |  38 +++++++-
>  2 files changed, 176 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
> index 177158ff2a40..1eef6869cae1 100644
> --- a/drivers/gpu/drm/drm_client_modeset.c
> +++ b/drivers/gpu/drm/drm_client_modeset.c
> @@ -83,6 +83,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client)
>  		}
>  		modeset->num_connectors = 0;
>  	}
> +
> +	kfree(client->properties);
> +	client->properties = NULL;
> +	client->num_properties = 0;

I failed to see why this code is in drm_client_modeset_release()
and not in drm_client_modeset_free().
In other words - why do we need to free properties in drm_client_modeset_probe()
which is the only other user of drm_client_modeset_release().

>  }
>  
>  void drm_client_modeset_free(struct drm_client_dev *client)
> @@ -879,6 +883,132 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>  }
>  EXPORT_SYMBOL(drm_client_modeset_probe);
>  
> +/**
> + * drm_client_modeset_set() - Set modeset configuration
> + * @client: DRM client
> + * @connector: Connector
> + * @mode: Display mode
> + * @fb: Framebuffer
> + *
> + * This function releases any current modeset info, including properties, and
> + * sets the new modeset in the client's modeset array.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb)
> +{
> +	struct drm_mode_set *modeset;
> +	int ret = -ENOENT;
> +
Need to check if atomic is supported?
Or maybe I just do not get all this atomic stuff yet..

> +	mutex_lock(&client->modeset_mutex);
> +
> +	drm_client_modeset_release(client);
If the check below fails - is it then correct to release modeset?
> +
> +	if (!connector || !mode || !fb) {
> +		ret = 0;
Error out, it is not a success if we cannot do anything?

> +		goto unlock;
> +	}
> +
> +	drm_client_for_each_modeset(modeset, client) {
> +		if (!connector_has_possible_crtc(connector, modeset->crtc))
> +			continue;
> +
> +		modeset->mode = drm_mode_duplicate(client->dev, mode);
> +		if (!modeset->mode) {
> +			ret = -ENOMEM;
> +			break;
> +		}
> +
> +		drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V);
> +
> +		drm_connector_get(connector);
> +		modeset->connectors[modeset->num_connectors++] = connector;
> +
> +		modeset->fb = fb;
> +		ret = 0;
> +		break;
> +	}
> +unlock:
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_modeset_set);
> +
> +/**
> + * drm_client_modeset_set_property() - Set a property on the current configuration
> + * @client: DRM client
> + * @obj: DRM Mode Object
> + * @prop: DRM Property
> + * @value: Property value
> + *
> + * Note: Currently only implemented for atomic drivers.
Are there any reason to in the future support legacy (non-atomic)
drivers.
If not then reword - as the above reads like it is on a TODO list to
support legacy drivers.

> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
> +				    struct drm_property *prop, u64 value)
> +{
> +	struct drm_client_property *properties;
> +	int ret = 0;
> +
> +	if (!prop)
> +		return -EINVAL;
> +
Need to check if atomic is supported?
Or maybe I just do not get all this atomic stuff yet..

> +	mutex_lock(&client->modeset_mutex);
> +
> +	properties = krealloc(client->properties,
> +			      (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL);
> +	if (!properties) {
> +		ret = -ENOMEM;
> +		goto unlock;
> +	}
> +
> +	properties[client->num_properties].obj = obj;
> +	properties[client->num_properties].prop = prop;
The drm_client_modeset_set_property() take over ownership of prop.
This looks wrong - should this be a copy of prop?
properties[].prop should not be a pointer but a full drm_property
struct?

> +	properties[client->num_properties].value = value;
> +	client->properties = properties;
> +	client->num_properties++;
> +unlock:
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_client_modeset_set_property);
> +
> +/**
> + * drm_client_modeset_set_rotation() - Set rotation on the current configuration
> + * @client: DRM client
> + * @value: Rotation value
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value)
> +{
> +	struct drm_plane *plane = NULL;
> +	struct drm_mode_set *modeset;
> +
> +	mutex_lock(&client->modeset_mutex);
> +	drm_client_for_each_modeset(modeset, client) {
> +		if (modeset->mode) {
> +			plane = modeset->crtc->primary;
> +			break;
> +		}
> +	}
> +	mutex_unlock(&client->modeset_mutex);
> +
> +	if (!plane)
> +		return -ENOENT;
> +
> +	return drm_client_modeset_set_property(client, &plane->base,
> +					       plane->rotation_property, value);
> +}
> +EXPORT_SYMBOL(drm_client_modeset_set_rotation);
> +
>  /**
>   * drm_client_rotation() - Check the initial rotation value
>   * @modeset: DRM modeset
> @@ -973,6 +1103,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>  	struct drm_atomic_state *state;
>  	struct drm_modeset_acquire_ctx ctx;
>  	struct drm_mode_set *mode_set;
> +	unsigned int i;
>  	int ret;
>  
>  	drm_modeset_acquire_init(&ctx, 0);
> @@ -1033,6 +1164,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>  		}
>  	}
>  
> +	for (i = 0; i < client->num_properties; i++) {
> +		struct drm_client_property *prop = &client->properties[i];
> +
> +		ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value);
> +		if (ret)
> +			goto out_state;
> +	}
> +
With the code above drm_atomic_set_property() is called also when check
is true. I had expected that check would not change anything.

>  	if (check)
>  		ret = drm_atomic_check_only(state);
>  	else
> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
> index b6ffa4863e45..4b266741ec0e 100644
> --- a/include/drm/drm_client.h
> +++ b/include/drm/drm_client.h
> @@ -16,6 +16,7 @@ struct drm_file;
>  struct drm_framebuffer;
>  struct drm_gem_object;
>  struct drm_minor;
> +struct drm_property;
>  struct module;
>  
>  /**
> @@ -64,6 +65,26 @@ struct drm_client_funcs {
>  	int (*hotplug)(struct drm_client_dev *client);
>  };
>  
> +/**
> + * struct drm_client_property - DRM client property
> + */
> +struct drm_client_property {
> +	/**
> +	 * @obj: DRM Mode Object to which the property belongs.
> +	 */
> +	struct drm_mode_object *obj;
> +
> +	/**
> +	 * @prop: DRM Property.
> +	 */
> +	struct drm_property *prop;
> +
> +	/**
> +	 * @value: Property value.
> +	 */
> +	u64 value;
> +};
> +
>  /**
>   * struct drm_client_dev - DRM client instance
>   */
> @@ -97,7 +118,7 @@ struct drm_client_dev {
>  	struct drm_file *file;
>  
>  	/**
> -	 * @modeset_mutex: Protects @modesets.
> +	 * @modeset_mutex: Protects @modesets and @properties.
>  	 */
>  	struct mutex modeset_mutex;
>  
> @@ -105,6 +126,16 @@ struct drm_client_dev {
>  	 * @modesets: CRTC configurations
>  	 */
>  	struct drm_mode_set *modesets;
> +
> +	/**
> +	 * @properties: DRM properties attached to the configuration.
> +	 */
> +	struct drm_client_property *properties;
> +
> +	/**
> +	 * @num_properties: Number of attached properties.
> +	 */
> +	unsigned int num_properties;
>  };
>  
>  int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
> @@ -163,6 +194,11 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>  int drm_client_modeset_create(struct drm_client_dev *client);
>  void drm_client_modeset_free(struct drm_client_dev *client);
>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb);
> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
> +				    struct drm_property *prop, u64 value);
> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value);
>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
>  int drm_client_modeset_check(struct drm_client_dev *client);
>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 04/10] drm/client: Add drm_client_framebuffer_flush()
  2020-05-03  7:48     ` Sam Ravnborg
@ 2020-05-03  9:54       ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03  9:54 UTC (permalink / raw)
  To: Sam Ravnborg; +Cc: dri-devel, linux-usb



Den 03.05.2020 09.48, skrev Sam Ravnborg:
> Hi Noralf.
> 
> On Wed, Apr 29, 2020 at 02:48:24PM +0200, Noralf Trønnes wrote:
>> Some drivers need explicit flushing of buffer changes, add a function
>> that does that.
> I trust you on this.

All drivers in tiny/ and at least udl need to be told to flush changes.
For userspace this happens either by calling DRM_IOCTL_MODE_DIRTYFB or
doing a page/buffer flip DRM_IOCTL_MODE_PAGE_FLIP or do a
DRM_IOCTL_MODE_ATOMIC (can contain damage report using plane property
FB_DAMAGE_CLIPS). For drivers that use drm_gem_fb_create_with_dirty()
and the drm_damage_helper (all of them now I think) this will result in
an atomic commit. The driver can use drm_atomic_helper_damage_merged()
to get the combined damage rectangle.

Noralf.

> 
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Some bikeshedding below. Either way:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> 
>> ---
>>  drivers/gpu/drm/drm_client.c | 31 +++++++++++++++++++++++++++++++
>>  include/drm/drm_client.h     |  1 +
>>  2 files changed, 32 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
>> index cb5ee9f1ffaa..8dbc2ecdcaea 100644
>> --- a/drivers/gpu/drm/drm_client.c
>> +++ b/drivers/gpu/drm/drm_client.c
>> @@ -483,6 +483,37 @@ void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
>>  }
>>  EXPORT_SYMBOL(drm_client_framebuffer_delete);
>>  
>> +/**
>> + * drm_client_framebuffer_flush - Manually flush client framebuffer
>> + * @buffer: DRM client buffer (can be NULL)
>> + * @rect: Damage rectangle (if NULL flushes all)
>> + *
>> + * This calls &drm_framebuffer_funcs->dirty (if present) to flush buffer changes
>> + * for drivers that need it.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
> 
> Alternative proposal - that I think is simpler.
> But both variants works for me.
>> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect)
>> +{
> 	struct drm_framebuffer_funcs *funcs;
> 	struct drm_clip_rect clip;
>> +
>> +	if (!buffer || !buffer->fb || !buffer->fb->funcs->dirty)
>> +		return 0;
> 	funcs = buffer->fb->funcs;
>> +
>> +	if (rect) {
>> +		clip.x1 = rect->x1;
>> +		clip.y1 = rect->y1;
>> +		clip.x2 = rect->x2;
>> +		clip.y2 = rect->y2;
> 		return funcs->dirty(buffer->fb, buffer->client->file,
> 				    0, 0, &clip, 1);
>> +	} else {
> 		return funcs->dirty(buffer->fb, buffer->client->file,
> 				    0, 0, NULL, 0);
> 	}
> 
>> +
>> +	return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
>> +					0, 0, clip, clip ? 1 : 0);
>> +}
>> +EXPORT_SYMBOL(drm_client_framebuffer_flush);
>> +
>>  #ifdef CONFIG_DEBUG_FS
>>  static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
>>  {
>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>> index bbb5689fa9a8..6ef5364d6dfb 100644
>> --- a/include/drm/drm_client.h
>> +++ b/include/drm/drm_client.h
>> @@ -156,6 +156,7 @@ struct drm_client_buffer {
>>  struct drm_client_buffer *
>>  drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format);
>>  void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
>> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect);
>>  void *drm_client_buffer_vmap(struct drm_client_buffer *buffer);
>>  void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>>  
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 04/10] drm/client: Add drm_client_framebuffer_flush()
@ 2020-05-03  9:54       ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03  9:54 UTC (permalink / raw)
  To: Sam Ravnborg; +Cc: linux-usb, dri-devel



Den 03.05.2020 09.48, skrev Sam Ravnborg:
> Hi Noralf.
> 
> On Wed, Apr 29, 2020 at 02:48:24PM +0200, Noralf Trønnes wrote:
>> Some drivers need explicit flushing of buffer changes, add a function
>> that does that.
> I trust you on this.

All drivers in tiny/ and at least udl need to be told to flush changes.
For userspace this happens either by calling DRM_IOCTL_MODE_DIRTYFB or
doing a page/buffer flip DRM_IOCTL_MODE_PAGE_FLIP or do a
DRM_IOCTL_MODE_ATOMIC (can contain damage report using plane property
FB_DAMAGE_CLIPS). For drivers that use drm_gem_fb_create_with_dirty()
and the drm_damage_helper (all of them now I think) this will result in
an atomic commit. The driver can use drm_atomic_helper_damage_merged()
to get the combined damage rectangle.

Noralf.

> 
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Some bikeshedding below. Either way:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> 
>> ---
>>  drivers/gpu/drm/drm_client.c | 31 +++++++++++++++++++++++++++++++
>>  include/drm/drm_client.h     |  1 +
>>  2 files changed, 32 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
>> index cb5ee9f1ffaa..8dbc2ecdcaea 100644
>> --- a/drivers/gpu/drm/drm_client.c
>> +++ b/drivers/gpu/drm/drm_client.c
>> @@ -483,6 +483,37 @@ void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
>>  }
>>  EXPORT_SYMBOL(drm_client_framebuffer_delete);
>>  
>> +/**
>> + * drm_client_framebuffer_flush - Manually flush client framebuffer
>> + * @buffer: DRM client buffer (can be NULL)
>> + * @rect: Damage rectangle (if NULL flushes all)
>> + *
>> + * This calls &drm_framebuffer_funcs->dirty (if present) to flush buffer changes
>> + * for drivers that need it.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
> 
> Alternative proposal - that I think is simpler.
> But both variants works for me.
>> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect)
>> +{
> 	struct drm_framebuffer_funcs *funcs;
> 	struct drm_clip_rect clip;
>> +
>> +	if (!buffer || !buffer->fb || !buffer->fb->funcs->dirty)
>> +		return 0;
> 	funcs = buffer->fb->funcs;
>> +
>> +	if (rect) {
>> +		clip.x1 = rect->x1;
>> +		clip.y1 = rect->y1;
>> +		clip.x2 = rect->x2;
>> +		clip.y2 = rect->y2;
> 		return funcs->dirty(buffer->fb, buffer->client->file,
> 				    0, 0, &clip, 1);
>> +	} else {
> 		return funcs->dirty(buffer->fb, buffer->client->file,
> 				    0, 0, NULL, 0);
> 	}
> 
>> +
>> +	return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
>> +					0, 0, clip, clip ? 1 : 0);
>> +}
>> +EXPORT_SYMBOL(drm_client_framebuffer_flush);
>> +
>>  #ifdef CONFIG_DEBUG_FS
>>  static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
>>  {
>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>> index bbb5689fa9a8..6ef5364d6dfb 100644
>> --- a/include/drm/drm_client.h
>> +++ b/include/drm/drm_client.h
>> @@ -156,6 +156,7 @@ struct drm_client_buffer {
>>  struct drm_client_buffer *
>>  drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format);
>>  void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
>> +int drm_client_framebuffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect);
>>  void *drm_client_buffer_vmap(struct drm_client_buffer *buffer);
>>  void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>>  
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 05/10] drm/client: Add drm_client_modeset_check()
  2020-05-03  8:03     ` Sam Ravnborg
@ 2020-05-03 10:02       ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03 10:02 UTC (permalink / raw)
  To: Sam Ravnborg; +Cc: dri-devel, linux-usb



Den 03.05.2020 10.03, skrev Sam Ravnborg:
> Hi Noralf.
> 
> On Wed, Apr 29, 2020 at 02:48:25PM +0200, Noralf Trønnes wrote:
>> Add a way for client to check the configuration before comitting.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Two small ntis. With these addressed:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
>> ---
>>  drivers/gpu/drm/drm_client_modeset.c | 35 ++++++++++++++++++++++++----
>>  include/drm/drm_client.h             |  1 +
>>  2 files changed, 32 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
>> index 7443114bd713..177158ff2a40 100644
>> --- a/drivers/gpu/drm/drm_client_modeset.c
>> +++ b/drivers/gpu/drm/drm_client_modeset.c
>> @@ -966,7 +966,7 @@ bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
>>  }
>>  EXPORT_SYMBOL(drm_client_rotation);
>>  
>> -static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active)
>> +static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
>>  {
>>  	struct drm_device *dev = client->dev;
>>  	struct drm_plane *plane;
>> @@ -1033,7 +1033,10 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>>  		}
>>  	}
>>  
>> -	ret = drm_atomic_commit(state);
>> +	if (check)
>> +		ret = drm_atomic_check_only(state);
>> +	else
>> +		ret = drm_atomic_commit(state);
>>  
>>  out_state:
>>  	if (ret == -EDEADLK)
>> @@ -1094,6 +1097,30 @@ static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
>>  	return ret;
>>  }
>>  
>> +/**
>> + * drm_client_modeset_check() - Check CRTC configuration
> This part of the comment does not match the description below.
> 
> 
>> + * @client: DRM client
>> + *
>> + * Check modeset configuration.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_check(struct drm_client_dev *client)
>> +{
>> +	int ret;
>> +
>> +	if (!drm_drv_uses_atomic_modeset(client->dev))
>> +		return 0;
> If client does not use atomic the check should fail - no?

Returning an error here would result in the client not working with
non-atomic drivers which AFAIK doesn't have a way to check the state
before hand. The client have to commit the state/configuration to find
out if it is good.

Noralf.

> 
>> +
>> +	mutex_lock(&client->modeset_mutex);
>> +	ret = drm_client_modeset_commit_atomic(client, true, true);
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_check);
>> +
>>  /**
>>   * drm_client_modeset_commit_locked() - Force commit CRTC configuration
>>   * @client: DRM client
>> @@ -1112,7 +1139,7 @@ int drm_client_modeset_commit_locked(struct drm_client_dev *client)
>>  
>>  	mutex_lock(&client->modeset_mutex);
>>  	if (drm_drv_uses_atomic_modeset(dev))
>> -		ret = drm_client_modeset_commit_atomic(client, true);
>> +		ret = drm_client_modeset_commit_atomic(client, true, false);
>>  	else
>>  		ret = drm_client_modeset_commit_legacy(client);
>>  	mutex_unlock(&client->modeset_mutex);
>> @@ -1188,7 +1215,7 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
>>  
>>  	mutex_lock(&client->modeset_mutex);
>>  	if (drm_drv_uses_atomic_modeset(dev))
>> -		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON);
>> +		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
>>  	else
>>  		drm_client_modeset_dpms_legacy(client, mode);
>>  	mutex_unlock(&client->modeset_mutex);
>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>> index 6ef5364d6dfb..b6ffa4863e45 100644
>> --- a/include/drm/drm_client.h
>> +++ b/include/drm/drm_client.h
>> @@ -164,6 +164,7 @@ int drm_client_modeset_create(struct drm_client_dev *client);
>>  void drm_client_modeset_free(struct drm_client_dev *client);
>>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
>>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
>> +int drm_client_modeset_check(struct drm_client_dev *client);
>>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
>>  int drm_client_modeset_commit(struct drm_client_dev *client);
>>  int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 05/10] drm/client: Add drm_client_modeset_check()
@ 2020-05-03 10:02       ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03 10:02 UTC (permalink / raw)
  To: Sam Ravnborg; +Cc: linux-usb, dri-devel



Den 03.05.2020 10.03, skrev Sam Ravnborg:
> Hi Noralf.
> 
> On Wed, Apr 29, 2020 at 02:48:25PM +0200, Noralf Trønnes wrote:
>> Add a way for client to check the configuration before comitting.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Two small ntis. With these addressed:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
>> ---
>>  drivers/gpu/drm/drm_client_modeset.c | 35 ++++++++++++++++++++++++----
>>  include/drm/drm_client.h             |  1 +
>>  2 files changed, 32 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
>> index 7443114bd713..177158ff2a40 100644
>> --- a/drivers/gpu/drm/drm_client_modeset.c
>> +++ b/drivers/gpu/drm/drm_client_modeset.c
>> @@ -966,7 +966,7 @@ bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
>>  }
>>  EXPORT_SYMBOL(drm_client_rotation);
>>  
>> -static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active)
>> +static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
>>  {
>>  	struct drm_device *dev = client->dev;
>>  	struct drm_plane *plane;
>> @@ -1033,7 +1033,10 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>>  		}
>>  	}
>>  
>> -	ret = drm_atomic_commit(state);
>> +	if (check)
>> +		ret = drm_atomic_check_only(state);
>> +	else
>> +		ret = drm_atomic_commit(state);
>>  
>>  out_state:
>>  	if (ret == -EDEADLK)
>> @@ -1094,6 +1097,30 @@ static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
>>  	return ret;
>>  }
>>  
>> +/**
>> + * drm_client_modeset_check() - Check CRTC configuration
> This part of the comment does not match the description below.
> 
> 
>> + * @client: DRM client
>> + *
>> + * Check modeset configuration.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_check(struct drm_client_dev *client)
>> +{
>> +	int ret;
>> +
>> +	if (!drm_drv_uses_atomic_modeset(client->dev))
>> +		return 0;
> If client does not use atomic the check should fail - no?

Returning an error here would result in the client not working with
non-atomic drivers which AFAIK doesn't have a way to check the state
before hand. The client have to commit the state/configuration to find
out if it is good.

Noralf.

> 
>> +
>> +	mutex_lock(&client->modeset_mutex);
>> +	ret = drm_client_modeset_commit_atomic(client, true, true);
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_check);
>> +
>>  /**
>>   * drm_client_modeset_commit_locked() - Force commit CRTC configuration
>>   * @client: DRM client
>> @@ -1112,7 +1139,7 @@ int drm_client_modeset_commit_locked(struct drm_client_dev *client)
>>  
>>  	mutex_lock(&client->modeset_mutex);
>>  	if (drm_drv_uses_atomic_modeset(dev))
>> -		ret = drm_client_modeset_commit_atomic(client, true);
>> +		ret = drm_client_modeset_commit_atomic(client, true, false);
>>  	else
>>  		ret = drm_client_modeset_commit_legacy(client);
>>  	mutex_unlock(&client->modeset_mutex);
>> @@ -1188,7 +1215,7 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
>>  
>>  	mutex_lock(&client->modeset_mutex);
>>  	if (drm_drv_uses_atomic_modeset(dev))
>> -		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON);
>> +		ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
>>  	else
>>  		drm_client_modeset_dpms_legacy(client, mode);
>>  	mutex_unlock(&client->modeset_mutex);
>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>> index 6ef5364d6dfb..b6ffa4863e45 100644
>> --- a/include/drm/drm_client.h
>> +++ b/include/drm/drm_client.h
>> @@ -164,6 +164,7 @@ int drm_client_modeset_create(struct drm_client_dev *client);
>>  void drm_client_modeset_free(struct drm_client_dev *client);
>>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
>>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
>> +int drm_client_modeset_check(struct drm_client_dev *client);
>>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
>>  int drm_client_modeset_commit(struct drm_client_dev *client);
>>  int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 07/10] drm/format-helper: Add drm_fb_swab()
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-05-03 10:29     ` Sam Ravnborg
  -1 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03 10:29 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-usb

Hi Noralf

On Wed, Apr 29, 2020 at 02:48:27PM +0200, Noralf Trønnes wrote:
> This replaces drm_fb_swab16() with drm_fb_swab() supporting 16 and 32-bit.
> Also make pixel line caching optional.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
A couple of nits, with these considered:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>

> ---
>  drivers/gpu/drm/drm_format_helper.c | 61 +++++++++++++++++++----------
>  drivers/gpu/drm/drm_mipi_dbi.c      |  2 +-
>  include/drm/drm_format_helper.h     |  4 +-
>  3 files changed, 44 insertions(+), 23 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
> index 0897cb9aeaff..5c147c49668c 100644
> --- a/drivers/gpu/drm/drm_format_helper.c
> +++ b/drivers/gpu/drm/drm_format_helper.c
> @@ -79,39 +79,60 @@ void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>  EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
>  
>  /**
> - * drm_fb_swab16 - Swap bytes into clip buffer
> - * @dst: RGB565 destination buffer
> - * @vaddr: RGB565 source buffer
> + * drm_fb_swab - Swap bytes into clip buffer
> + * @dst: Destination buffer
> + * @src: Source buffer
>   * @fb: DRM framebuffer
>   * @clip: Clip rectangle area to copy
> + * @cached: Source buffer is mapped cached (eg. not write-combined)
> + *
> + * If @cached is false a temporary buffer is used to cache one pixel line at a
> + * time to speed up slow uncached reads.
> + *
> + * This function does not apply clipping on dst, i.e. the destination
> + * is a small buffer containing the clip rect only.
>   */
> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
> -		   struct drm_rect *clip)
> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
> +		 struct drm_rect *clip, bool cached)
>  {
> -	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
> +	u8 cpp = fb->format->cpp[0];
Use of format->cpp is deprecated, should be char_per_block according to
the comment in drm_fourcc.h

> +	size_t len = drm_rect_width(clip) * cpp;
> +	u16 *src16, *dst16 = dst;
> +	u32 *src32, *dst32 = dst;
>  	unsigned int x, y;
> -	u16 *src, *buf;
> +	void *buf = NULL;
>  
> -	/*
> -	 * The cma memory is write-combined so reads are uncached.
> -	 * Speed up by fetching one line at a time.
> -	 */
> -	buf = kmalloc(len, GFP_KERNEL);
> -	if (!buf)
> +	if (WARN_ON_ONCE(cpp == 1))
>  		return;
Or cpp != 2 && cpp != 4?
>  
> +	if (!cached)
> +		buf = kmalloc(len, GFP_KERNEL);
> +
> +	src += clip_offset(clip, fb->pitches[0], cpp);
Good that drm_rect_width() and clip_offset() are used,
replacing open-coded variants.

> +
>  	for (y = clip->y1; y < clip->y2; y++) {
> -		src = vaddr + (y * fb->pitches[0]);
> -		src += clip->x1;
> -		memcpy(buf, src, len);
> -		src = buf;
> -		for (x = clip->x1; x < clip->x2; x++)
> -			*dst++ = swab16(*src++);
> +		if (buf) {
> +			memcpy(buf, src, len);
> +			src16 = buf;
> +			src32 = buf;
> +		} else {
> +			src16 = src;
> +			src32 = src;
> +		}
> +
> +		for (x = clip->x1; x < clip->x2; x++) {
> +			if (cpp == 4)
> +				*dst32++ = swab32(*src32++);
> +			else
> +				*dst16++ = swab16(*src16++);
> +		}
> +
> +		src += fb->pitches[0];
>  	}
>  
>  	kfree(buf);
>  }
> -EXPORT_SYMBOL(drm_fb_swab16);
> +EXPORT_SYMBOL(drm_fb_swab);
>  
>  static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
>  					   unsigned int pixels,
> diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c
> index 16bff1be4b8a..bfefbcb69287 100644
> --- a/drivers/gpu/drm/drm_mipi_dbi.c
> +++ b/drivers/gpu/drm/drm_mipi_dbi.c
> @@ -217,7 +217,7 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
>  	switch (fb->format->format) {
>  	case DRM_FORMAT_RGB565:
>  		if (swap)
> -			drm_fb_swab16(dst, src, fb, clip);
> +			drm_fb_swab(dst, src, fb, clip, !import_attach);
>  		else
>  			drm_fb_memcpy(dst, src, fb, clip);
>  		break;
> diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h
> index ac220aa1a245..5f9e37032468 100644
> --- a/include/drm/drm_format_helper.h
> +++ b/include/drm/drm_format_helper.h
> @@ -14,8 +14,8 @@ void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
>  void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>  			   struct drm_framebuffer *fb,
>  			   struct drm_rect *clip);
> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
> -		   struct drm_rect *clip);
> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
> +		 struct drm_rect *clip, bool cached);
>  void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
>  			       struct drm_framebuffer *fb,
>  			       struct drm_rect *clip, bool swab);
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 07/10] drm/format-helper: Add drm_fb_swab()
@ 2020-05-03 10:29     ` Sam Ravnborg
  0 siblings, 0 replies; 66+ messages in thread
From: Sam Ravnborg @ 2020-05-03 10:29 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: linux-usb, dri-devel

Hi Noralf

On Wed, Apr 29, 2020 at 02:48:27PM +0200, Noralf Trønnes wrote:
> This replaces drm_fb_swab16() with drm_fb_swab() supporting 16 and 32-bit.
> Also make pixel line caching optional.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
A couple of nits, with these considered:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>

> ---
>  drivers/gpu/drm/drm_format_helper.c | 61 +++++++++++++++++++----------
>  drivers/gpu/drm/drm_mipi_dbi.c      |  2 +-
>  include/drm/drm_format_helper.h     |  4 +-
>  3 files changed, 44 insertions(+), 23 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
> index 0897cb9aeaff..5c147c49668c 100644
> --- a/drivers/gpu/drm/drm_format_helper.c
> +++ b/drivers/gpu/drm/drm_format_helper.c
> @@ -79,39 +79,60 @@ void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>  EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
>  
>  /**
> - * drm_fb_swab16 - Swap bytes into clip buffer
> - * @dst: RGB565 destination buffer
> - * @vaddr: RGB565 source buffer
> + * drm_fb_swab - Swap bytes into clip buffer
> + * @dst: Destination buffer
> + * @src: Source buffer
>   * @fb: DRM framebuffer
>   * @clip: Clip rectangle area to copy
> + * @cached: Source buffer is mapped cached (eg. not write-combined)
> + *
> + * If @cached is false a temporary buffer is used to cache one pixel line at a
> + * time to speed up slow uncached reads.
> + *
> + * This function does not apply clipping on dst, i.e. the destination
> + * is a small buffer containing the clip rect only.
>   */
> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
> -		   struct drm_rect *clip)
> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
> +		 struct drm_rect *clip, bool cached)
>  {
> -	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
> +	u8 cpp = fb->format->cpp[0];
Use of format->cpp is deprecated, should be char_per_block according to
the comment in drm_fourcc.h

> +	size_t len = drm_rect_width(clip) * cpp;
> +	u16 *src16, *dst16 = dst;
> +	u32 *src32, *dst32 = dst;
>  	unsigned int x, y;
> -	u16 *src, *buf;
> +	void *buf = NULL;
>  
> -	/*
> -	 * The cma memory is write-combined so reads are uncached.
> -	 * Speed up by fetching one line at a time.
> -	 */
> -	buf = kmalloc(len, GFP_KERNEL);
> -	if (!buf)
> +	if (WARN_ON_ONCE(cpp == 1))
>  		return;
Or cpp != 2 && cpp != 4?
>  
> +	if (!cached)
> +		buf = kmalloc(len, GFP_KERNEL);
> +
> +	src += clip_offset(clip, fb->pitches[0], cpp);
Good that drm_rect_width() and clip_offset() are used,
replacing open-coded variants.

> +
>  	for (y = clip->y1; y < clip->y2; y++) {
> -		src = vaddr + (y * fb->pitches[0]);
> -		src += clip->x1;
> -		memcpy(buf, src, len);
> -		src = buf;
> -		for (x = clip->x1; x < clip->x2; x++)
> -			*dst++ = swab16(*src++);
> +		if (buf) {
> +			memcpy(buf, src, len);
> +			src16 = buf;
> +			src32 = buf;
> +		} else {
> +			src16 = src;
> +			src32 = src;
> +		}
> +
> +		for (x = clip->x1; x < clip->x2; x++) {
> +			if (cpp == 4)
> +				*dst32++ = swab32(*src32++);
> +			else
> +				*dst16++ = swab16(*src16++);
> +		}
> +
> +		src += fb->pitches[0];
>  	}
>  
>  	kfree(buf);
>  }
> -EXPORT_SYMBOL(drm_fb_swab16);
> +EXPORT_SYMBOL(drm_fb_swab);
>  
>  static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
>  					   unsigned int pixels,
> diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c
> index 16bff1be4b8a..bfefbcb69287 100644
> --- a/drivers/gpu/drm/drm_mipi_dbi.c
> +++ b/drivers/gpu/drm/drm_mipi_dbi.c
> @@ -217,7 +217,7 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
>  	switch (fb->format->format) {
>  	case DRM_FORMAT_RGB565:
>  		if (swap)
> -			drm_fb_swab16(dst, src, fb, clip);
> +			drm_fb_swab(dst, src, fb, clip, !import_attach);
>  		else
>  			drm_fb_memcpy(dst, src, fb, clip);
>  		break;
> diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h
> index ac220aa1a245..5f9e37032468 100644
> --- a/include/drm/drm_format_helper.h
> +++ b/include/drm/drm_format_helper.h
> @@ -14,8 +14,8 @@ void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
>  void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>  			   struct drm_framebuffer *fb,
>  			   struct drm_rect *clip);
> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
> -		   struct drm_rect *clip);
> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
> +		 struct drm_rect *clip, bool cached);
>  void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
>  			       struct drm_framebuffer *fb,
>  			       struct drm_rect *clip, bool swab);
> -- 
> 2.23.0
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 06/10] drm/client: Add a way to set modeset, properties and rotation
  2020-05-03  8:47     ` Sam Ravnborg
@ 2020-05-03 10:50       ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03 10:50 UTC (permalink / raw)
  To: Sam Ravnborg, Daniel Vetter; +Cc: dri-devel, linux-usb



Den 03.05.2020 10.47, skrev Sam Ravnborg:
> Hi Noralf.
> 
> Some comments in the following - partly because I still do not fully
> grasp modeset etc.

I still don't fully understand it either. I have wandered inside the
atomic machinery countless times to track what happens to this or that
state/property and 2 weeks later the insight is gone. It just doesn't
stick. What makes it difficult for me I believe is that I have never
looked much at the userspace side, how this is all used.

> 
> 	Sam
> 
> On Wed, Apr 29, 2020 at 02:48:26PM +0200, Noralf Trønnes wrote:
>> This adds functions for clients that need more control over the
>> configuration than what's setup by drm_client_modeset_probe().
>> Connector, fb and display mode can be set using drm_client_modeset_set().
>> Plane rotation can be set using drm_client_modeset_set_rotation() and
>> other properties using drm_client_modeset_set_property(). Property
>> setting is only implemented for atomic drivers.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>  drivers/gpu/drm/drm_client_modeset.c | 139 +++++++++++++++++++++++++++
>>  include/drm/drm_client.h             |  38 +++++++-
>>  2 files changed, 176 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
>> index 177158ff2a40..1eef6869cae1 100644
>> --- a/drivers/gpu/drm/drm_client_modeset.c
>> +++ b/drivers/gpu/drm/drm_client_modeset.c
>> @@ -83,6 +83,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client)
>>  		}
>>  		modeset->num_connectors = 0;
>>  	}
>> +
>> +	kfree(client->properties);
>> +	client->properties = NULL;
>> +	client->num_properties = 0;
> 
> I failed to see why this code is in drm_client_modeset_release()
> and not in drm_client_modeset_free().
> In other words - why do we need to free properties in drm_client_modeset_probe()
> which is the only other user of drm_client_modeset_release().

This is legacy behaviour that was moved over from drm_fb_helper. It
cleared the modeset before probing for a new useable modeset. So the
same applies when the client sets the modeset itself, we need to clear
out the previous modeset.

If drm_client was written from scratch, the state/modeset would not be
stored in drm_client_dev, but stored by the client.

With the current situation a client can't check a configuration while
retaining a working config/state. It looses the working state setting up
one for testing.

This is not a problem for my usecase, but it can be for a future
usecase. Since it works for me I decided to keep it as-is instead of
trying to fix this without knowing what the future usecase might be.

> 
>>  }
>>  
>>  void drm_client_modeset_free(struct drm_client_dev *client)
>> @@ -879,6 +883,132 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>>  }
>>  EXPORT_SYMBOL(drm_client_modeset_probe);
>>  
>> +/**
>> + * drm_client_modeset_set() - Set modeset configuration
>> + * @client: DRM client
>> + * @connector: Connector
>> + * @mode: Display mode
>> + * @fb: Framebuffer
>> + *
>> + * This function releases any current modeset info, including properties, and
>> + * sets the new modeset in the client's modeset array.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
>> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb)
>> +{
>> +	struct drm_mode_set *modeset;
>> +	int ret = -ENOENT;
>> +
> Need to check if atomic is supported?

Not needed here because drm_client_modeset_commit() supports
legacy/non-atomic.

> Or maybe I just do not get all this atomic stuff yet..
> 
>> +	mutex_lock(&client->modeset_mutex);
>> +
>> +	drm_client_modeset_release(client);
> If the check below fails - is it then correct to release modeset?
>> +
>> +	if (!connector || !mode || !fb) {
>> +		ret = 0;
> Error out, it is not a success if we cannot do anything?

Ah, I haven't documented this. This actually clears the modesets. If
this is commited, it results in all outputs being turned off.
Maybe I should do a drm_client_modeset_clear() instead.

> 
>> +		goto unlock;
>> +	}
>> +
>> +	drm_client_for_each_modeset(modeset, client) {
>> +		if (!connector_has_possible_crtc(connector, modeset->crtc))
>> +			continue;
>> +
>> +		modeset->mode = drm_mode_duplicate(client->dev, mode);
>> +		if (!modeset->mode) {
>> +			ret = -ENOMEM;
>> +			break;
>> +		}
>> +
>> +		drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V);
>> +
>> +		drm_connector_get(connector);
>> +		modeset->connectors[modeset->num_connectors++] = connector;
>> +
>> +		modeset->fb = fb;
>> +		ret = 0;
>> +		break;
>> +	}
>> +unlock:
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_set);
>> +
>> +/**
>> + * drm_client_modeset_set_property() - Set a property on the current configuration
>> + * @client: DRM client
>> + * @obj: DRM Mode Object
>> + * @prop: DRM Property
>> + * @value: Property value
>> + *
>> + * Note: Currently only implemented for atomic drivers.
> Are there any reason to in the future support legacy (non-atomic)
> drivers.
> If not then reword - as the above reads like it is on a TODO list to
> support legacy drivers.

It is possible to implement for legacy drivers in
drm_client_modeset_commit_legacy() like the plane property is done, but
I'm not going to do it. So it's not a TODO, but should imply a possibility.

If you a have a better way to express that I'm all ears.

> 
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
>> +				    struct drm_property *prop, u64 value)
>> +{
>> +	struct drm_client_property *properties;
>> +	int ret = 0;
>> +
>> +	if (!prop)
>> +		return -EINVAL;
>> +
> Need to check if atomic is supported?
> Or maybe I just do not get all this atomic stuff yet..

Indeed, I should follow up on the docs and return -EOPNOTSUPP.

> 
>> +	mutex_lock(&client->modeset_mutex);
>> +
>> +	properties = krealloc(client->properties,
>> +			      (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL);
>> +	if (!properties) {
>> +		ret = -ENOMEM;
>> +		goto unlock;
>> +	}
>> +
>> +	properties[client->num_properties].obj = obj;
>> +	properties[client->num_properties].prop = prop;
> The drm_client_modeset_set_property() take over ownership of prop.
> This looks wrong - should this be a copy of prop?
> properties[].prop should not be a pointer but a full drm_property
> struct?

prop is stored in the drm_mode_config object and is immutable so we
don't take ownership, but merely get a ref to it. For atomic the
property value is stored in the state. See
drm_atomic_connector_set_property() as an example.

Example use:

drm_client_modeset_set_property(client, &connector->base,
config->tv_hue_property, val);


> 
>> +	properties[client->num_properties].value = value;
>> +	client->properties = properties;
>> +	client->num_properties++;
>> +unlock:
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_set_property);
>> +
>> +/**
>> + * drm_client_modeset_set_rotation() - Set rotation on the current configuration
>> + * @client: DRM client
>> + * @value: Rotation value
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value)
>> +{
>> +	struct drm_plane *plane = NULL;
>> +	struct drm_mode_set *modeset;
>> +
>> +	mutex_lock(&client->modeset_mutex);
>> +	drm_client_for_each_modeset(modeset, client) {
>> +		if (modeset->mode) {
>> +			plane = modeset->crtc->primary;
>> +			break;
>> +		}
>> +	}
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	if (!plane)
>> +		return -ENOENT;
>> +
>> +	return drm_client_modeset_set_property(client, &plane->base,
>> +					       plane->rotation_property, value);
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_set_rotation);
>> +
>>  /**
>>   * drm_client_rotation() - Check the initial rotation value
>>   * @modeset: DRM modeset
>> @@ -973,6 +1103,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>>  	struct drm_atomic_state *state;
>>  	struct drm_modeset_acquire_ctx ctx;
>>  	struct drm_mode_set *mode_set;
>> +	unsigned int i;
>>  	int ret;
>>  
>>  	drm_modeset_acquire_init(&ctx, 0);
>> @@ -1033,6 +1164,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>>  		}
>>  	}
>>  
>> +	for (i = 0; i < client->num_properties; i++) {
>> +		struct drm_client_property *prop = &client->properties[i];
>> +
>> +		ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value);
>> +		if (ret)
>> +			goto out_state;
>> +	}
>> +
> With the code above drm_atomic_set_property() is called also when check
> is true. I had expected that check would not change anything.

If you look at this function in full, you see that we allocate a state
and set properties on that state. So we only change this local state. We
need to set the properties when checking so see if the driver accepts them.

If all drivers where atomic, we could let the client have access to
drm_atomic_state instead of storing modesets and properties in
drm_client_dev. Maybe we could have a drm_client_state structure to hold
modesets and properties, I don't know. Maybe Daniel can chime in here,
if we want to fix this now or leave it to a future user that needs to
check one config while having a working config.

Noralf.

> 
>>  	if (check)
>>  		ret = drm_atomic_check_only(state);
>>  	else
>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>> index b6ffa4863e45..4b266741ec0e 100644
>> --- a/include/drm/drm_client.h
>> +++ b/include/drm/drm_client.h
>> @@ -16,6 +16,7 @@ struct drm_file;
>>  struct drm_framebuffer;
>>  struct drm_gem_object;
>>  struct drm_minor;
>> +struct drm_property;
>>  struct module;
>>  
>>  /**
>> @@ -64,6 +65,26 @@ struct drm_client_funcs {
>>  	int (*hotplug)(struct drm_client_dev *client);
>>  };
>>  
>> +/**
>> + * struct drm_client_property - DRM client property
>> + */
>> +struct drm_client_property {
>> +	/**
>> +	 * @obj: DRM Mode Object to which the property belongs.
>> +	 */
>> +	struct drm_mode_object *obj;
>> +
>> +	/**
>> +	 * @prop: DRM Property.
>> +	 */
>> +	struct drm_property *prop;
>> +
>> +	/**
>> +	 * @value: Property value.
>> +	 */
>> +	u64 value;
>> +};
>> +
>>  /**
>>   * struct drm_client_dev - DRM client instance
>>   */
>> @@ -97,7 +118,7 @@ struct drm_client_dev {
>>  	struct drm_file *file;
>>  
>>  	/**
>> -	 * @modeset_mutex: Protects @modesets.
>> +	 * @modeset_mutex: Protects @modesets and @properties.
>>  	 */
>>  	struct mutex modeset_mutex;
>>  
>> @@ -105,6 +126,16 @@ struct drm_client_dev {
>>  	 * @modesets: CRTC configurations
>>  	 */
>>  	struct drm_mode_set *modesets;
>> +
>> +	/**
>> +	 * @properties: DRM properties attached to the configuration.
>> +	 */
>> +	struct drm_client_property *properties;
>> +
>> +	/**
>> +	 * @num_properties: Number of attached properties.
>> +	 */
>> +	unsigned int num_properties;
>>  };
>>  
>>  int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
>> @@ -163,6 +194,11 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>>  int drm_client_modeset_create(struct drm_client_dev *client);
>>  void drm_client_modeset_free(struct drm_client_dev *client);
>>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
>> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
>> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb);
>> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
>> +				    struct drm_property *prop, u64 value);
>> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value);
>>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
>>  int drm_client_modeset_check(struct drm_client_dev *client);
>>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 06/10] drm/client: Add a way to set modeset, properties and rotation
@ 2020-05-03 10:50       ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03 10:50 UTC (permalink / raw)
  To: Sam Ravnborg, Daniel Vetter; +Cc: linux-usb, dri-devel



Den 03.05.2020 10.47, skrev Sam Ravnborg:
> Hi Noralf.
> 
> Some comments in the following - partly because I still do not fully
> grasp modeset etc.

I still don't fully understand it either. I have wandered inside the
atomic machinery countless times to track what happens to this or that
state/property and 2 weeks later the insight is gone. It just doesn't
stick. What makes it difficult for me I believe is that I have never
looked much at the userspace side, how this is all used.

> 
> 	Sam
> 
> On Wed, Apr 29, 2020 at 02:48:26PM +0200, Noralf Trønnes wrote:
>> This adds functions for clients that need more control over the
>> configuration than what's setup by drm_client_modeset_probe().
>> Connector, fb and display mode can be set using drm_client_modeset_set().
>> Plane rotation can be set using drm_client_modeset_set_rotation() and
>> other properties using drm_client_modeset_set_property(). Property
>> setting is only implemented for atomic drivers.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>  drivers/gpu/drm/drm_client_modeset.c | 139 +++++++++++++++++++++++++++
>>  include/drm/drm_client.h             |  38 +++++++-
>>  2 files changed, 176 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
>> index 177158ff2a40..1eef6869cae1 100644
>> --- a/drivers/gpu/drm/drm_client_modeset.c
>> +++ b/drivers/gpu/drm/drm_client_modeset.c
>> @@ -83,6 +83,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client)
>>  		}
>>  		modeset->num_connectors = 0;
>>  	}
>> +
>> +	kfree(client->properties);
>> +	client->properties = NULL;
>> +	client->num_properties = 0;
> 
> I failed to see why this code is in drm_client_modeset_release()
> and not in drm_client_modeset_free().
> In other words - why do we need to free properties in drm_client_modeset_probe()
> which is the only other user of drm_client_modeset_release().

This is legacy behaviour that was moved over from drm_fb_helper. It
cleared the modeset before probing for a new useable modeset. So the
same applies when the client sets the modeset itself, we need to clear
out the previous modeset.

If drm_client was written from scratch, the state/modeset would not be
stored in drm_client_dev, but stored by the client.

With the current situation a client can't check a configuration while
retaining a working config/state. It looses the working state setting up
one for testing.

This is not a problem for my usecase, but it can be for a future
usecase. Since it works for me I decided to keep it as-is instead of
trying to fix this without knowing what the future usecase might be.

> 
>>  }
>>  
>>  void drm_client_modeset_free(struct drm_client_dev *client)
>> @@ -879,6 +883,132 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
>>  }
>>  EXPORT_SYMBOL(drm_client_modeset_probe);
>>  
>> +/**
>> + * drm_client_modeset_set() - Set modeset configuration
>> + * @client: DRM client
>> + * @connector: Connector
>> + * @mode: Display mode
>> + * @fb: Framebuffer
>> + *
>> + * This function releases any current modeset info, including properties, and
>> + * sets the new modeset in the client's modeset array.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
>> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb)
>> +{
>> +	struct drm_mode_set *modeset;
>> +	int ret = -ENOENT;
>> +
> Need to check if atomic is supported?

Not needed here because drm_client_modeset_commit() supports
legacy/non-atomic.

> Or maybe I just do not get all this atomic stuff yet..
> 
>> +	mutex_lock(&client->modeset_mutex);
>> +
>> +	drm_client_modeset_release(client);
> If the check below fails - is it then correct to release modeset?
>> +
>> +	if (!connector || !mode || !fb) {
>> +		ret = 0;
> Error out, it is not a success if we cannot do anything?

Ah, I haven't documented this. This actually clears the modesets. If
this is commited, it results in all outputs being turned off.
Maybe I should do a drm_client_modeset_clear() instead.

> 
>> +		goto unlock;
>> +	}
>> +
>> +	drm_client_for_each_modeset(modeset, client) {
>> +		if (!connector_has_possible_crtc(connector, modeset->crtc))
>> +			continue;
>> +
>> +		modeset->mode = drm_mode_duplicate(client->dev, mode);
>> +		if (!modeset->mode) {
>> +			ret = -ENOMEM;
>> +			break;
>> +		}
>> +
>> +		drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V);
>> +
>> +		drm_connector_get(connector);
>> +		modeset->connectors[modeset->num_connectors++] = connector;
>> +
>> +		modeset->fb = fb;
>> +		ret = 0;
>> +		break;
>> +	}
>> +unlock:
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_set);
>> +
>> +/**
>> + * drm_client_modeset_set_property() - Set a property on the current configuration
>> + * @client: DRM client
>> + * @obj: DRM Mode Object
>> + * @prop: DRM Property
>> + * @value: Property value
>> + *
>> + * Note: Currently only implemented for atomic drivers.
> Are there any reason to in the future support legacy (non-atomic)
> drivers.
> If not then reword - as the above reads like it is on a TODO list to
> support legacy drivers.

It is possible to implement for legacy drivers in
drm_client_modeset_commit_legacy() like the plane property is done, but
I'm not going to do it. So it's not a TODO, but should imply a possibility.

If you a have a better way to express that I'm all ears.

> 
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
>> +				    struct drm_property *prop, u64 value)
>> +{
>> +	struct drm_client_property *properties;
>> +	int ret = 0;
>> +
>> +	if (!prop)
>> +		return -EINVAL;
>> +
> Need to check if atomic is supported?
> Or maybe I just do not get all this atomic stuff yet..

Indeed, I should follow up on the docs and return -EOPNOTSUPP.

> 
>> +	mutex_lock(&client->modeset_mutex);
>> +
>> +	properties = krealloc(client->properties,
>> +			      (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL);
>> +	if (!properties) {
>> +		ret = -ENOMEM;
>> +		goto unlock;
>> +	}
>> +
>> +	properties[client->num_properties].obj = obj;
>> +	properties[client->num_properties].prop = prop;
> The drm_client_modeset_set_property() take over ownership of prop.
> This looks wrong - should this be a copy of prop?
> properties[].prop should not be a pointer but a full drm_property
> struct?

prop is stored in the drm_mode_config object and is immutable so we
don't take ownership, but merely get a ref to it. For atomic the
property value is stored in the state. See
drm_atomic_connector_set_property() as an example.

Example use:

drm_client_modeset_set_property(client, &connector->base,
config->tv_hue_property, val);


> 
>> +	properties[client->num_properties].value = value;
>> +	client->properties = properties;
>> +	client->num_properties++;
>> +unlock:
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_set_property);
>> +
>> +/**
>> + * drm_client_modeset_set_rotation() - Set rotation on the current configuration
>> + * @client: DRM client
>> + * @value: Rotation value
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value)
>> +{
>> +	struct drm_plane *plane = NULL;
>> +	struct drm_mode_set *modeset;
>> +
>> +	mutex_lock(&client->modeset_mutex);
>> +	drm_client_for_each_modeset(modeset, client) {
>> +		if (modeset->mode) {
>> +			plane = modeset->crtc->primary;
>> +			break;
>> +		}
>> +	}
>> +	mutex_unlock(&client->modeset_mutex);
>> +
>> +	if (!plane)
>> +		return -ENOENT;
>> +
>> +	return drm_client_modeset_set_property(client, &plane->base,
>> +					       plane->rotation_property, value);
>> +}
>> +EXPORT_SYMBOL(drm_client_modeset_set_rotation);
>> +
>>  /**
>>   * drm_client_rotation() - Check the initial rotation value
>>   * @modeset: DRM modeset
>> @@ -973,6 +1103,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>>  	struct drm_atomic_state *state;
>>  	struct drm_modeset_acquire_ctx ctx;
>>  	struct drm_mode_set *mode_set;
>> +	unsigned int i;
>>  	int ret;
>>  
>>  	drm_modeset_acquire_init(&ctx, 0);
>> @@ -1033,6 +1164,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
>>  		}
>>  	}
>>  
>> +	for (i = 0; i < client->num_properties; i++) {
>> +		struct drm_client_property *prop = &client->properties[i];
>> +
>> +		ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value);
>> +		if (ret)
>> +			goto out_state;
>> +	}
>> +
> With the code above drm_atomic_set_property() is called also when check
> is true. I had expected that check would not change anything.

If you look at this function in full, you see that we allocate a state
and set properties on that state. So we only change this local state. We
need to set the properties when checking so see if the driver accepts them.

If all drivers where atomic, we could let the client have access to
drm_atomic_state instead of storing modesets and properties in
drm_client_dev. Maybe we could have a drm_client_state structure to hold
modesets and properties, I don't know. Maybe Daniel can chime in here,
if we want to fix this now or leave it to a future user that needs to
check one config while having a working config.

Noralf.

> 
>>  	if (check)
>>  		ret = drm_atomic_check_only(state);
>>  	else
>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>> index b6ffa4863e45..4b266741ec0e 100644
>> --- a/include/drm/drm_client.h
>> +++ b/include/drm/drm_client.h
>> @@ -16,6 +16,7 @@ struct drm_file;
>>  struct drm_framebuffer;
>>  struct drm_gem_object;
>>  struct drm_minor;
>> +struct drm_property;
>>  struct module;
>>  
>>  /**
>> @@ -64,6 +65,26 @@ struct drm_client_funcs {
>>  	int (*hotplug)(struct drm_client_dev *client);
>>  };
>>  
>> +/**
>> + * struct drm_client_property - DRM client property
>> + */
>> +struct drm_client_property {
>> +	/**
>> +	 * @obj: DRM Mode Object to which the property belongs.
>> +	 */
>> +	struct drm_mode_object *obj;
>> +
>> +	/**
>> +	 * @prop: DRM Property.
>> +	 */
>> +	struct drm_property *prop;
>> +
>> +	/**
>> +	 * @value: Property value.
>> +	 */
>> +	u64 value;
>> +};
>> +
>>  /**
>>   * struct drm_client_dev - DRM client instance
>>   */
>> @@ -97,7 +118,7 @@ struct drm_client_dev {
>>  	struct drm_file *file;
>>  
>>  	/**
>> -	 * @modeset_mutex: Protects @modesets.
>> +	 * @modeset_mutex: Protects @modesets and @properties.
>>  	 */
>>  	struct mutex modeset_mutex;
>>  
>> @@ -105,6 +126,16 @@ struct drm_client_dev {
>>  	 * @modesets: CRTC configurations
>>  	 */
>>  	struct drm_mode_set *modesets;
>> +
>> +	/**
>> +	 * @properties: DRM properties attached to the configuration.
>> +	 */
>> +	struct drm_client_property *properties;
>> +
>> +	/**
>> +	 * @num_properties: Number of attached properties.
>> +	 */
>> +	unsigned int num_properties;
>>  };
>>  
>>  int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
>> @@ -163,6 +194,11 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
>>  int drm_client_modeset_create(struct drm_client_dev *client);
>>  void drm_client_modeset_free(struct drm_client_dev *client);
>>  int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
>> +int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
>> +			   struct drm_display_mode *mode, struct drm_framebuffer *fb);
>> +int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
>> +				    struct drm_property *prop, u64 value);
>> +int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value);
>>  bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
>>  int drm_client_modeset_check(struct drm_client_dev *client);
>>  int drm_client_modeset_commit_locked(struct drm_client_dev *client);
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 07/10] drm/format-helper: Add drm_fb_swab()
  2020-05-03 10:29     ` Sam Ravnborg
@ 2020-05-03 13:38       ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03 13:38 UTC (permalink / raw)
  To: Sam Ravnborg; +Cc: dri-devel, linux-usb



Den 03.05.2020 12.29, skrev Sam Ravnborg:
> Hi Noralf
> 
> On Wed, Apr 29, 2020 at 02:48:27PM +0200, Noralf Trønnes wrote:
>> This replaces drm_fb_swab16() with drm_fb_swab() supporting 16 and 32-bit.
>> Also make pixel line caching optional.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> A couple of nits, with these considered:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> 
>> ---
>>  drivers/gpu/drm/drm_format_helper.c | 61 +++++++++++++++++++----------
>>  drivers/gpu/drm/drm_mipi_dbi.c      |  2 +-
>>  include/drm/drm_format_helper.h     |  4 +-
>>  3 files changed, 44 insertions(+), 23 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
>> index 0897cb9aeaff..5c147c49668c 100644
>> --- a/drivers/gpu/drm/drm_format_helper.c
>> +++ b/drivers/gpu/drm/drm_format_helper.c
>> @@ -79,39 +79,60 @@ void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>>  EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
>>  
>>  /**
>> - * drm_fb_swab16 - Swap bytes into clip buffer
>> - * @dst: RGB565 destination buffer
>> - * @vaddr: RGB565 source buffer
>> + * drm_fb_swab - Swap bytes into clip buffer
>> + * @dst: Destination buffer
>> + * @src: Source buffer
>>   * @fb: DRM framebuffer
>>   * @clip: Clip rectangle area to copy
>> + * @cached: Source buffer is mapped cached (eg. not write-combined)
>> + *
>> + * If @cached is false a temporary buffer is used to cache one pixel line at a
>> + * time to speed up slow uncached reads.
>> + *
>> + * This function does not apply clipping on dst, i.e. the destination
>> + * is a small buffer containing the clip rect only.
>>   */
>> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
>> -		   struct drm_rect *clip)
>> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
>> +		 struct drm_rect *clip, bool cached)
>>  {
>> -	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
>> +	u8 cpp = fb->format->cpp[0];
> Use of format->cpp is deprecated, should be char_per_block according to
> the comment in drm_fourcc.h

I ducked this because if I had to do it properly I would have to look at
block width/height as well and yes ensure that num_planes is 1. That
would leave me with writing a helper function for something I don't
really understand :-)

static inline bool
drm_format_info_is_WHAT_TO_CALL_THIS(const struct drm_format_info *info)
{
	return info->num_planes == 1 &&
	       drm_format_info_block_width(info, 0) == 1 &&
	       drm_format_info_block_height(info, 0) == 1;
}

Or I could ofc just spell out the full assert inside this function:

	info->num_planes == 1 &&
	drm_format_info_block_width(info, 0) == 1 &&
	drm_format_info_block_height(info, 0) == 1 &&
	info->char_per_block[0] == 2 &&
	info->char_per_block[0] == 4

That way I don't have to know what I'm actually checking.
I'm using drm_fb_swab() for RGB formats, but it can be used for any
format that meets the above criteria.

And maybe I should check .hsub and .vsub as well, I don't know.

cpp was such a nice simple concept :-) So I'll keep it unless someone
knowledgeable shines some light on this.

> 
>> +	size_t len = drm_rect_width(clip) * cpp;
>> +	u16 *src16, *dst16 = dst;
>> +	u32 *src32, *dst32 = dst;
>>  	unsigned int x, y;
>> -	u16 *src, *buf;
>> +	void *buf = NULL;
>>  
>> -	/*
>> -	 * The cma memory is write-combined so reads are uncached.
>> -	 * Speed up by fetching one line at a time.
>> -	 */
>> -	buf = kmalloc(len, GFP_KERNEL);
>> -	if (!buf)
>> +	if (WARN_ON_ONCE(cpp == 1))
>>  		return;
> Or cpp != 2 && cpp != 4?

Indeed, I agree.

Noralf.

>>  
>> +	if (!cached)
>> +		buf = kmalloc(len, GFP_KERNEL);
>> +
>> +	src += clip_offset(clip, fb->pitches[0], cpp);
> Good that drm_rect_width() and clip_offset() are used,
> replacing open-coded variants.
> 
>> +
>>  	for (y = clip->y1; y < clip->y2; y++) {
>> -		src = vaddr + (y * fb->pitches[0]);
>> -		src += clip->x1;
>> -		memcpy(buf, src, len);
>> -		src = buf;
>> -		for (x = clip->x1; x < clip->x2; x++)
>> -			*dst++ = swab16(*src++);
>> +		if (buf) {
>> +			memcpy(buf, src, len);
>> +			src16 = buf;
>> +			src32 = buf;
>> +		} else {
>> +			src16 = src;
>> +			src32 = src;
>> +		}
>> +
>> +		for (x = clip->x1; x < clip->x2; x++) {
>> +			if (cpp == 4)
>> +				*dst32++ = swab32(*src32++);
>> +			else
>> +				*dst16++ = swab16(*src16++);
>> +		}
>> +
>> +		src += fb->pitches[0];
>>  	}
>>  
>>  	kfree(buf);
>>  }
>> -EXPORT_SYMBOL(drm_fb_swab16);
>> +EXPORT_SYMBOL(drm_fb_swab);
>>  
>>  static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
>>  					   unsigned int pixels,
>> diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c
>> index 16bff1be4b8a..bfefbcb69287 100644
>> --- a/drivers/gpu/drm/drm_mipi_dbi.c
>> +++ b/drivers/gpu/drm/drm_mipi_dbi.c
>> @@ -217,7 +217,7 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
>>  	switch (fb->format->format) {
>>  	case DRM_FORMAT_RGB565:
>>  		if (swap)
>> -			drm_fb_swab16(dst, src, fb, clip);
>> +			drm_fb_swab(dst, src, fb, clip, !import_attach);
>>  		else
>>  			drm_fb_memcpy(dst, src, fb, clip);
>>  		break;
>> diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h
>> index ac220aa1a245..5f9e37032468 100644
>> --- a/include/drm/drm_format_helper.h
>> +++ b/include/drm/drm_format_helper.h
>> @@ -14,8 +14,8 @@ void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
>>  void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>>  			   struct drm_framebuffer *fb,
>>  			   struct drm_rect *clip);
>> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
>> -		   struct drm_rect *clip);
>> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
>> +		 struct drm_rect *clip, bool cached);
>>  void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
>>  			       struct drm_framebuffer *fb,
>>  			       struct drm_rect *clip, bool swab);
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 07/10] drm/format-helper: Add drm_fb_swab()
@ 2020-05-03 13:38       ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-03 13:38 UTC (permalink / raw)
  To: Sam Ravnborg; +Cc: linux-usb, dri-devel



Den 03.05.2020 12.29, skrev Sam Ravnborg:
> Hi Noralf
> 
> On Wed, Apr 29, 2020 at 02:48:27PM +0200, Noralf Trønnes wrote:
>> This replaces drm_fb_swab16() with drm_fb_swab() supporting 16 and 32-bit.
>> Also make pixel line caching optional.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> A couple of nits, with these considered:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> 
>> ---
>>  drivers/gpu/drm/drm_format_helper.c | 61 +++++++++++++++++++----------
>>  drivers/gpu/drm/drm_mipi_dbi.c      |  2 +-
>>  include/drm/drm_format_helper.h     |  4 +-
>>  3 files changed, 44 insertions(+), 23 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
>> index 0897cb9aeaff..5c147c49668c 100644
>> --- a/drivers/gpu/drm/drm_format_helper.c
>> +++ b/drivers/gpu/drm/drm_format_helper.c
>> @@ -79,39 +79,60 @@ void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>>  EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
>>  
>>  /**
>> - * drm_fb_swab16 - Swap bytes into clip buffer
>> - * @dst: RGB565 destination buffer
>> - * @vaddr: RGB565 source buffer
>> + * drm_fb_swab - Swap bytes into clip buffer
>> + * @dst: Destination buffer
>> + * @src: Source buffer
>>   * @fb: DRM framebuffer
>>   * @clip: Clip rectangle area to copy
>> + * @cached: Source buffer is mapped cached (eg. not write-combined)
>> + *
>> + * If @cached is false a temporary buffer is used to cache one pixel line at a
>> + * time to speed up slow uncached reads.
>> + *
>> + * This function does not apply clipping on dst, i.e. the destination
>> + * is a small buffer containing the clip rect only.
>>   */
>> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
>> -		   struct drm_rect *clip)
>> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
>> +		 struct drm_rect *clip, bool cached)
>>  {
>> -	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
>> +	u8 cpp = fb->format->cpp[0];
> Use of format->cpp is deprecated, should be char_per_block according to
> the comment in drm_fourcc.h

I ducked this because if I had to do it properly I would have to look at
block width/height as well and yes ensure that num_planes is 1. That
would leave me with writing a helper function for something I don't
really understand :-)

static inline bool
drm_format_info_is_WHAT_TO_CALL_THIS(const struct drm_format_info *info)
{
	return info->num_planes == 1 &&
	       drm_format_info_block_width(info, 0) == 1 &&
	       drm_format_info_block_height(info, 0) == 1;
}

Or I could ofc just spell out the full assert inside this function:

	info->num_planes == 1 &&
	drm_format_info_block_width(info, 0) == 1 &&
	drm_format_info_block_height(info, 0) == 1 &&
	info->char_per_block[0] == 2 &&
	info->char_per_block[0] == 4

That way I don't have to know what I'm actually checking.
I'm using drm_fb_swab() for RGB formats, but it can be used for any
format that meets the above criteria.

And maybe I should check .hsub and .vsub as well, I don't know.

cpp was such a nice simple concept :-) So I'll keep it unless someone
knowledgeable shines some light on this.

> 
>> +	size_t len = drm_rect_width(clip) * cpp;
>> +	u16 *src16, *dst16 = dst;
>> +	u32 *src32, *dst32 = dst;
>>  	unsigned int x, y;
>> -	u16 *src, *buf;
>> +	void *buf = NULL;
>>  
>> -	/*
>> -	 * The cma memory is write-combined so reads are uncached.
>> -	 * Speed up by fetching one line at a time.
>> -	 */
>> -	buf = kmalloc(len, GFP_KERNEL);
>> -	if (!buf)
>> +	if (WARN_ON_ONCE(cpp == 1))
>>  		return;
> Or cpp != 2 && cpp != 4?

Indeed, I agree.

Noralf.

>>  
>> +	if (!cached)
>> +		buf = kmalloc(len, GFP_KERNEL);
>> +
>> +	src += clip_offset(clip, fb->pitches[0], cpp);
> Good that drm_rect_width() and clip_offset() are used,
> replacing open-coded variants.
> 
>> +
>>  	for (y = clip->y1; y < clip->y2; y++) {
>> -		src = vaddr + (y * fb->pitches[0]);
>> -		src += clip->x1;
>> -		memcpy(buf, src, len);
>> -		src = buf;
>> -		for (x = clip->x1; x < clip->x2; x++)
>> -			*dst++ = swab16(*src++);
>> +		if (buf) {
>> +			memcpy(buf, src, len);
>> +			src16 = buf;
>> +			src32 = buf;
>> +		} else {
>> +			src16 = src;
>> +			src32 = src;
>> +		}
>> +
>> +		for (x = clip->x1; x < clip->x2; x++) {
>> +			if (cpp == 4)
>> +				*dst32++ = swab32(*src32++);
>> +			else
>> +				*dst16++ = swab16(*src16++);
>> +		}
>> +
>> +		src += fb->pitches[0];
>>  	}
>>  
>>  	kfree(buf);
>>  }
>> -EXPORT_SYMBOL(drm_fb_swab16);
>> +EXPORT_SYMBOL(drm_fb_swab);
>>  
>>  static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
>>  					   unsigned int pixels,
>> diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c
>> index 16bff1be4b8a..bfefbcb69287 100644
>> --- a/drivers/gpu/drm/drm_mipi_dbi.c
>> +++ b/drivers/gpu/drm/drm_mipi_dbi.c
>> @@ -217,7 +217,7 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
>>  	switch (fb->format->format) {
>>  	case DRM_FORMAT_RGB565:
>>  		if (swap)
>> -			drm_fb_swab16(dst, src, fb, clip);
>> +			drm_fb_swab(dst, src, fb, clip, !import_attach);
>>  		else
>>  			drm_fb_memcpy(dst, src, fb, clip);
>>  		break;
>> diff --git a/include/drm/drm_format_helper.h b/include/drm/drm_format_helper.h
>> index ac220aa1a245..5f9e37032468 100644
>> --- a/include/drm/drm_format_helper.h
>> +++ b/include/drm/drm_format_helper.h
>> @@ -14,8 +14,8 @@ void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
>>  void drm_fb_memcpy_dstclip(void __iomem *dst, void *vaddr,
>>  			   struct drm_framebuffer *fb,
>>  			   struct drm_rect *clip);
>> -void drm_fb_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
>> -		   struct drm_rect *clip);
>> +void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
>> +		 struct drm_rect *clip, bool cached);
>>  void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
>>  			       struct drm_framebuffer *fb,
>>  			       struct drm_rect *clip, bool swab);
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
  2020-04-30 14:02           ` Daniel Vetter
@ 2020-05-04  7:10             ` Lee Jones
  -1 siblings, 0 replies; 66+ messages in thread
From: Lee Jones @ 2020-05-04  7:10 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Noralf Trønnes, Jingoo Han, Daniel Thompson, linux-usb, dri-devel

On Thu, 30 Apr 2020, Daniel Vetter wrote:

> On Thu, Apr 30, 2020 at 11:15:29AM +0100, Lee Jones wrote:
> > On Thu, 30 Apr 2020, Noralf Trønnes wrote:
> > 
> > > 
> > > 
> > > Den 30.04.2020 10.32, skrev Lee Jones:
> > > > On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> > > > 
> > > >> Add a way to lookup a backlight device based on its name.
> > > >> Will be used by a USB display gadget getting the name from configfs.
> > > >>
> > > >> Cc: Lee Jones <lee.jones@linaro.org>
> > > >> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> > > >> Cc: Jingoo Han <jingoohan1@gmail.com>
> > > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> > > >> ---
> > > >>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
> > > >>  include/linux/backlight.h           |  1 +
> > > >>  2 files changed, 22 insertions(+)
> > > > 
> > > > Once reviewed, can this patch be applied on its own?
> > > > 
> > > 
> > > If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
> > > the driver won't be ready for that. This patch has this dependency
> > > chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
> > > gets easier.
> > > 
> > > > My guess is that it can't, as the other patches in this set depend on
> > > > it, right?  If this assumption is true, you need to send me the rest
> > > > of the set.
> > > > 
> > > > FYI: It's normally better to send the whole set to everyone, as it
> > > > provides visibility on current review (or lack there of) status of the
> > > > other patches and allows each of the maintainers to discuss possible
> > > > merge strategies.
> 
> Unfortunately this doesn't hold universally, since once you cc too many
> people smtp servers start throwing your mails away. Generally only happens
> for bigger refactorings, so pretty much anyone working cross-tree doesn't
> do this because it doesn't work.

I haven't experienced issues with SMTP servers.  Although I am aware
of a few mailing lists that are configured to require moderator
intervention if the recipient list reaches a given length.

> > > dri-devel is the ML for backlight so I assumed you got the full set.
> > 
> > dri-devel isn't the ML for Backlight.  It's an interested party.
> > 
> > I certainly have no intention of subscribing to it.
> 
> dri-devel is on lore so that you can grab missing patches. No need to
> subscribe. I've only manged to get this sorted recently (last autumn or
> so), but it's finally done.

This is helpful.  Thanks for doing the work required to make this
happen.  It's still infinitely more convenient to have the full set
in my inbox available for review.  As someone who works cross-
subsystem a lot, I can tell you that it works well in the vast
majority of cases.

Maybe just add the listed (in 'MAINTAINERS') maintainers and possibly
the reviewers.  Obviously all of the secondary interested parties that
get_maintainer.pl recommends should be omitted.

> > > I have had trouble in the past with my email provider dropping parts of
> > > a series when I had to many recipients.
> > 
> > Without visibility into the other patches in the set, things become
> > more difficult.  Maybe use a different/better email provider.

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH 01/10] backlight: Add backlight_device_get_by_name()
@ 2020-05-04  7:10             ` Lee Jones
  0 siblings, 0 replies; 66+ messages in thread
From: Lee Jones @ 2020-05-04  7:10 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: Jingoo Han, Daniel Thompson, linux-usb, dri-devel

On Thu, 30 Apr 2020, Daniel Vetter wrote:

> On Thu, Apr 30, 2020 at 11:15:29AM +0100, Lee Jones wrote:
> > On Thu, 30 Apr 2020, Noralf Trønnes wrote:
> > 
> > > 
> > > 
> > > Den 30.04.2020 10.32, skrev Lee Jones:
> > > > On Wed, 29 Apr 2020, Noralf Trønnes wrote:
> > > > 
> > > >> Add a way to lookup a backlight device based on its name.
> > > >> Will be used by a USB display gadget getting the name from configfs.
> > > >>
> > > >> Cc: Lee Jones <lee.jones@linaro.org>
> > > >> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> > > >> Cc: Jingoo Han <jingoohan1@gmail.com>
> > > >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> > > >> ---
> > > >>  drivers/video/backlight/backlight.c | 21 +++++++++++++++++++++
> > > >>  include/linux/backlight.h           |  1 +
> > > >>  2 files changed, 22 insertions(+)
> > > > 
> > > > Once reviewed, can this patch be applied on its own?
> > > > 
> > > 
> > > If you can apply it for 5.8, then we're good. DRM has cutoff at -rc5 and
> > > the driver won't be ready for that. This patch has this dependency
> > > chain: usb -> drm -> backlight. So if you can apply it for 5.8, things
> > > gets easier.
> > > 
> > > > My guess is that it can't, as the other patches in this set depend on
> > > > it, right?  If this assumption is true, you need to send me the rest
> > > > of the set.
> > > > 
> > > > FYI: It's normally better to send the whole set to everyone, as it
> > > > provides visibility on current review (or lack there of) status of the
> > > > other patches and allows each of the maintainers to discuss possible
> > > > merge strategies.
> 
> Unfortunately this doesn't hold universally, since once you cc too many
> people smtp servers start throwing your mails away. Generally only happens
> for bigger refactorings, so pretty much anyone working cross-tree doesn't
> do this because it doesn't work.

I haven't experienced issues with SMTP servers.  Although I am aware
of a few mailing lists that are configured to require moderator
intervention if the recipient list reaches a given length.

> > > dri-devel is the ML for backlight so I assumed you got the full set.
> > 
> > dri-devel isn't the ML for Backlight.  It's an interested party.
> > 
> > I certainly have no intention of subscribing to it.
> 
> dri-devel is on lore so that you can grab missing patches. No need to
> subscribe. I've only manged to get this sorted recently (last autumn or
> so), but it's finally done.

This is helpful.  Thanks for doing the work required to make this
happen.  It's still infinitely more convenient to have the full set
in my inbox available for review.  As someone who works cross-
subsystem a lot, I can tell you that it works well in the vast
majority of cases.

Maybe just add the listed (in 'MAINTAINERS') maintainers and possibly
the reviewers.  Obviously all of the secondary interested parties that
get_maintainer.pl recommends should be omitted.

> > > I have had trouble in the past with my email provider dropping parts of
> > > a series when I had to many recipients.
> > 
> > Without visibility into the other patches in the set, things become
> > more difficult.  Maybe use a different/better email provider.

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 02/10] drm: Add backlight helper
  2020-04-29 12:48   ` Noralf Trønnes
@ 2020-05-04 12:06     ` Daniel Vetter
  -1 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2020-05-04 12:06 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, linux-usb, Hans de Goede, Daniel Thompson

On Wed, Apr 29, 2020 at 02:48:22PM +0200, Noralf Trønnes wrote:
> This adds a function that creates a backlight device for a connector.
> It does not deal with the KMS backlight ABI proposition[1] to add a
> connector property. It only takes the current best practise to standardise
> the creation of a backlight device for DRM drivers while we wait for the
> property.
> 
> The brightness value is set using a connector state variable and an atomic
> commit.
> 
> I have looked through some of the backlight users and this is what I've found:
> 
> GNOME [2]
> ---------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual
> 
> Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
> Can connect connector and backlight using the sysfs device.
> 
> KDE [3]
> -------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual
> 
> Weston [4]
> ----------
> 
> Brightness range: 0-255
> Scale: Assumes perceptual
> 
> Chromium OS [5]
> ---------------
> 
> Brightness range: 0-100
> Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)
> 
> xserver [6]
> -----------
> 
> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
> Scale: Assumes perceptual
> 
> The builtin modesetting driver[7] does not support Backlight, Intel[8] does.
> 
> [1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
> [2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
> [3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
> [4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
> [5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
> [6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
> [7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
> [8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
> 
> Cc: Hans de Goede <hdegoede@redhat.com>
> Cc: Jani Nikula <jani.nikula@linux.intel.com>
> Cc: Martin Peres <martin.peres@linux.intel.com>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  Documentation/gpu/drm-kms-helpers.rst  |   6 +
>  drivers/gpu/drm/Kconfig                |   7 ++
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>  include/drm/drm_backlight_helper.h     |   9 ++
>  include/drm/drm_connector.h            |  10 ++
>  6 files changed, 187 insertions(+)
>  create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>  create mode 100644 include/drm/drm_backlight_helper.h
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 9668a7fe2408..29a2f4b49fd2 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>  
>  .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>     :export:
> +
> +Backlight Helper Reference
> +==========================
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
> +   :export:
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index d0aa6cff2e02..f6e13e18c9ca 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>  	help
>  	  Choose this if you need the GEM shmem helper functions
>  
> +config DRM_BACKLIGHT_HELPER
> +	bool
> +	depends on DRM
> +	select BACKLIGHT_CLASS_DEVICE
> +	help
> +	  Choose this if you need the backlight device helper functions
> +
>  config DRM_VM
>  	bool
>  	depends on DRM && MMU
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 6493088a0fdd..0d17662dde0a 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
>  drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>  drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>  drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>  
>  obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>  obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
> diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
> new file mode 100644
> index 000000000000..06e6a75d1d0a
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_backlight_helper.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +/*
> + * Copyright 2020 Noralf Trønnes
> + */
> +
> +#include <linux/backlight.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_file.h>
> +
> +static int drm_backlight_update_status(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_connector_state *connector_state;
> +	struct drm_device *dev = connector->dev;
> +	struct drm_modeset_acquire_ctx ctx;
> +	struct drm_atomic_state *state;
> +	int ret;
> +
> +	state = drm_atomic_state_alloc(dev);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	drm_modeset_acquire_init(&ctx, 0);
> +	state->acquire_ctx = &ctx;
> +retry:
> +	connector_state = drm_atomic_get_connector_state(state, connector);
> +	if (IS_ERR(connector_state)) {
> +		ret = PTR_ERR(connector_state);
> +		goto out;
> +	}
> +
> +	connector_state->backlight_brightness = bd->props.brightness;
> +
> +	ret = drm_atomic_commit(state);
> +out:
> +	if (ret == -EDEADLK) {
> +		drm_atomic_state_clear(state);
> +		drm_modeset_backoff(&ctx);
> +		goto retry;
> +	}
> +
> +	drm_atomic_state_put(state);
> +
> +	drm_modeset_drop_locks(&ctx);
> +	drm_modeset_acquire_fini(&ctx);
> +
> +	return ret;
> +}

Looking at this, I'm not sure this is generally useable outside of your
usb driver. Or stuff that does essentially the same.

For real hw drivers I expect a similar relationship between the kms driver
and the backlight driver like for i2c, clocks, power domaines, ... i.e.
the kms driver calls into the backlight driver. This also means that kms
locks will be nesting outside of the locks of these subordinate
subsystems, and hence direct access through other userspace interfaces
(like we have for i2c too) needs to bypass atomic kms. Otherwise we have
deadlock potential.


With your stuff here we can potentially get a backlight locks -> kms locks
-> backlight locks scenario, and lockdep will be angry about this. So in
general this doesn't work (I think, locking is hard), and in this specific
case it only works because your usb driver shovels everything over to
another machine. Lockdep will still complain if you load this together
with some other kms driver that uses backlight normally.

Aside: Locking is broken in the backlight code, we take the
bd->update_lock too late, after bd->props has already been handled
unsafely. But I guess that's a side effect of the sysfs interface being
racy by default. Or the backlight_enable/disable helpers should take
bd->ops_lock too.

So two things:
- I think the actual update needs to be pushed to a work_struct, with no
  flush_work, to avoid these locking loops completely.
- I think this is best left in your usb driver for now, until someone
  comes up with maybe an spi forwarder or whatever where we might need
  this again.

Or, and this is probably much simpler, just have a simpler backlight
driver that's completely orthogonal to the kms side, with some hw lock to
organize updates. For that case just adding a drm_connector->backlight
pointer, as prep for Hans' work, would be great.
-Daniel


> +
> +static int drm_backlight_get_brightness(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_device *dev = connector->dev;
> +	int brightness;
> +
> +	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
> +	brightness = connector->state->backlight_brightness;
> +	drm_modeset_unlock(&dev->mode_config.connection_mutex);
> +
> +	return brightness;
> +}
> +
> +static const struct backlight_ops drm_backlight_ops = {
> +	.get_brightness = drm_backlight_get_brightness,
> +	.update_status	= drm_backlight_update_status,
> +};
> +
> +/* Can be exported for drivers carrying a legacy name */
> +static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
> +{
> +	struct backlight_device *bd;
> +	const struct backlight_properties props = {
> +		.type = BACKLIGHT_RAW,
> +		.scale = BACKLIGHT_SCALE_NON_LINEAR,
> +		.max_brightness = 100,
> +	};
> +
> +	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
> +		    !drm_drv_uses_atomic_modeset(connector->dev) ||
> +		    !connector->kdev))
> +		return -EINVAL;
> +
> +	bd = backlight_device_register(name, connector->kdev, connector,
> +				       &drm_backlight_ops, &props);
> +	if (IS_ERR(bd))
> +		return PTR_ERR(bd);
> +
> +	connector->backlight = bd;
> +
> +	return 0;
> +}
> +
> +/**
> + * drm_backlight_register() - Register a backlight device for a connector
> + * @connector: Connector
> + *
> + * This function registers a backlight device for @connector with the following
> + * characteristics:
> + *
> + * - The connector sysfs device is used as a parent device for the backlight device.
> + *   Userspace can use this to connect backlight device and connector.
> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
> + * - Type is "raw"
> + * - Scale is "non-linear" (perceptual)
> + * - Max brightness is 100 giving a range of 0-100 inclusive
> + * - Reading sysfs **brightness** returns the backlight device property
> + * - Reading sysfs **actual_brightness** returns the connector state value
> + * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
> + *   be used to control power.
> + * - Backlight device suspend/resume events are ignored.
> + *
> + * Note:
> + *
> + * Brightness zero should not turn off backlight it should be the minimum
> + * brightness, DPMS handles power.
> + *
> + * This function must be called from &drm_connector_funcs->late_register() since
> + * it depends on the sysfs device.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_backlight_register(struct drm_connector *connector)
> +{
> +	const char *name = NULL;
> +	int ret;
> +
> +	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
> +			 connector->dev->primary->index, connector->name);
> +	if (!name)
> +		return -ENOMEM;
> +
> +	ret = drm_backlight_register_with_name(connector, name);
> +	kfree(name);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_backlight_register);
> +
> +/**
> + * drm_backlight_unregister() - Unregister backlight device
> + * @connector: Connector
> + *
> + * Unregister a backlight device. This must be called from the
> + * &drm_connector_funcs->early_unregister() callback.
> + */
> +void drm_backlight_unregister(struct drm_connector *connector)
> +{
> +	backlight_device_unregister(connector->backlight);
> +}
> +EXPORT_SYMBOL(drm_backlight_unregister);
> diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
> new file mode 100644
> index 000000000000..4151b66eb0b4
> --- /dev/null
> +++ b/include/drm/drm_backlight_helper.h
> @@ -0,0 +1,9 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> +
> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
> +
> +int drm_backlight_register(struct drm_connector *connector);
> +void drm_backlight_unregister(struct drm_connector *connector);
> +
> +#endif
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 221910948b37..ce678b694f45 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -32,6 +32,7 @@
>  
>  #include <uapi/drm/drm_mode.h>
>  
> +struct backlight_device;
>  struct drm_connector_helper_funcs;
>  struct drm_modeset_acquire_ctx;
>  struct drm_device;
> @@ -656,6 +657,12 @@ struct drm_connector_state {
>  	 */
>  	u8 max_bpc;
>  
> +	/**
> +	 * @backlight_brightness: Brightness value of the connector backlight
> +	 * device. See drm_backlight_register().
> +	 */
> +	u8 backlight_brightness;
> +
>  	/**
>  	 * @hdr_output_metadata:
>  	 * DRM blob property for HDR output metadata
> @@ -1422,6 +1429,9 @@ struct drm_connector {
>  
>  	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
>  	struct hdr_sink_metadata hdr_sink_metadata;
> +
> +	/** @backlight: Backlight device. See drm_backlight_register() */
> +	struct backlight_device *backlight;
>  };
>  
>  #define obj_to_connector(x) container_of(x, struct drm_connector, base)
> -- 
> 2.23.0
> 
> _______________________________________________
> 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] 66+ messages in thread

* Re: [PATCH 02/10] drm: Add backlight helper
@ 2020-05-04 12:06     ` Daniel Vetter
  0 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2020-05-04 12:06 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: Hans de Goede, Daniel Thompson, linux-usb, dri-devel

On Wed, Apr 29, 2020 at 02:48:22PM +0200, Noralf Trønnes wrote:
> This adds a function that creates a backlight device for a connector.
> It does not deal with the KMS backlight ABI proposition[1] to add a
> connector property. It only takes the current best practise to standardise
> the creation of a backlight device for DRM drivers while we wait for the
> property.
> 
> The brightness value is set using a connector state variable and an atomic
> commit.
> 
> I have looked through some of the backlight users and this is what I've found:
> 
> GNOME [2]
> ---------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual
> 
> Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
> Can connect connector and backlight using the sysfs device.
> 
> KDE [3]
> -------
> 
> Brightness range: 0-100
> Scale: Assumes perceptual
> 
> Weston [4]
> ----------
> 
> Brightness range: 0-255
> Scale: Assumes perceptual
> 
> Chromium OS [5]
> ---------------
> 
> Brightness range: 0-100
> Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)
> 
> xserver [6]
> -----------
> 
> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
> Scale: Assumes perceptual
> 
> The builtin modesetting driver[7] does not support Backlight, Intel[8] does.
> 
> [1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
> [2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
> [3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
> [4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
> [5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
> [6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
> [7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
> [8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
> 
> Cc: Hans de Goede <hdegoede@redhat.com>
> Cc: Jani Nikula <jani.nikula@linux.intel.com>
> Cc: Martin Peres <martin.peres@linux.intel.com>
> Cc: Daniel Thompson <daniel.thompson@linaro.org>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  Documentation/gpu/drm-kms-helpers.rst  |   6 +
>  drivers/gpu/drm/Kconfig                |   7 ++
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>  include/drm/drm_backlight_helper.h     |   9 ++
>  include/drm/drm_connector.h            |  10 ++
>  6 files changed, 187 insertions(+)
>  create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>  create mode 100644 include/drm/drm_backlight_helper.h
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 9668a7fe2408..29a2f4b49fd2 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>  
>  .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>     :export:
> +
> +Backlight Helper Reference
> +==========================
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
> +   :export:
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index d0aa6cff2e02..f6e13e18c9ca 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>  	help
>  	  Choose this if you need the GEM shmem helper functions
>  
> +config DRM_BACKLIGHT_HELPER
> +	bool
> +	depends on DRM
> +	select BACKLIGHT_CLASS_DEVICE
> +	help
> +	  Choose this if you need the backlight device helper functions
> +
>  config DRM_VM
>  	bool
>  	depends on DRM && MMU
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 6493088a0fdd..0d17662dde0a 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
>  drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>  drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>  drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>  
>  obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>  obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
> diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
> new file mode 100644
> index 000000000000..06e6a75d1d0a
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_backlight_helper.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +/*
> + * Copyright 2020 Noralf Trønnes
> + */
> +
> +#include <linux/backlight.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_file.h>
> +
> +static int drm_backlight_update_status(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_connector_state *connector_state;
> +	struct drm_device *dev = connector->dev;
> +	struct drm_modeset_acquire_ctx ctx;
> +	struct drm_atomic_state *state;
> +	int ret;
> +
> +	state = drm_atomic_state_alloc(dev);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	drm_modeset_acquire_init(&ctx, 0);
> +	state->acquire_ctx = &ctx;
> +retry:
> +	connector_state = drm_atomic_get_connector_state(state, connector);
> +	if (IS_ERR(connector_state)) {
> +		ret = PTR_ERR(connector_state);
> +		goto out;
> +	}
> +
> +	connector_state->backlight_brightness = bd->props.brightness;
> +
> +	ret = drm_atomic_commit(state);
> +out:
> +	if (ret == -EDEADLK) {
> +		drm_atomic_state_clear(state);
> +		drm_modeset_backoff(&ctx);
> +		goto retry;
> +	}
> +
> +	drm_atomic_state_put(state);
> +
> +	drm_modeset_drop_locks(&ctx);
> +	drm_modeset_acquire_fini(&ctx);
> +
> +	return ret;
> +}

Looking at this, I'm not sure this is generally useable outside of your
usb driver. Or stuff that does essentially the same.

For real hw drivers I expect a similar relationship between the kms driver
and the backlight driver like for i2c, clocks, power domaines, ... i.e.
the kms driver calls into the backlight driver. This also means that kms
locks will be nesting outside of the locks of these subordinate
subsystems, and hence direct access through other userspace interfaces
(like we have for i2c too) needs to bypass atomic kms. Otherwise we have
deadlock potential.


With your stuff here we can potentially get a backlight locks -> kms locks
-> backlight locks scenario, and lockdep will be angry about this. So in
general this doesn't work (I think, locking is hard), and in this specific
case it only works because your usb driver shovels everything over to
another machine. Lockdep will still complain if you load this together
with some other kms driver that uses backlight normally.

Aside: Locking is broken in the backlight code, we take the
bd->update_lock too late, after bd->props has already been handled
unsafely. But I guess that's a side effect of the sysfs interface being
racy by default. Or the backlight_enable/disable helpers should take
bd->ops_lock too.

So two things:
- I think the actual update needs to be pushed to a work_struct, with no
  flush_work, to avoid these locking loops completely.
- I think this is best left in your usb driver for now, until someone
  comes up with maybe an spi forwarder or whatever where we might need
  this again.

Or, and this is probably much simpler, just have a simpler backlight
driver that's completely orthogonal to the kms side, with some hw lock to
organize updates. For that case just adding a drm_connector->backlight
pointer, as prep for Hans' work, would be great.
-Daniel


> +
> +static int drm_backlight_get_brightness(struct backlight_device *bd)
> +{
> +	struct drm_connector *connector = bl_get_data(bd);
> +	struct drm_device *dev = connector->dev;
> +	int brightness;
> +
> +	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
> +	brightness = connector->state->backlight_brightness;
> +	drm_modeset_unlock(&dev->mode_config.connection_mutex);
> +
> +	return brightness;
> +}
> +
> +static const struct backlight_ops drm_backlight_ops = {
> +	.get_brightness = drm_backlight_get_brightness,
> +	.update_status	= drm_backlight_update_status,
> +};
> +
> +/* Can be exported for drivers carrying a legacy name */
> +static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
> +{
> +	struct backlight_device *bd;
> +	const struct backlight_properties props = {
> +		.type = BACKLIGHT_RAW,
> +		.scale = BACKLIGHT_SCALE_NON_LINEAR,
> +		.max_brightness = 100,
> +	};
> +
> +	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
> +		    !drm_drv_uses_atomic_modeset(connector->dev) ||
> +		    !connector->kdev))
> +		return -EINVAL;
> +
> +	bd = backlight_device_register(name, connector->kdev, connector,
> +				       &drm_backlight_ops, &props);
> +	if (IS_ERR(bd))
> +		return PTR_ERR(bd);
> +
> +	connector->backlight = bd;
> +
> +	return 0;
> +}
> +
> +/**
> + * drm_backlight_register() - Register a backlight device for a connector
> + * @connector: Connector
> + *
> + * This function registers a backlight device for @connector with the following
> + * characteristics:
> + *
> + * - The connector sysfs device is used as a parent device for the backlight device.
> + *   Userspace can use this to connect backlight device and connector.
> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
> + * - Type is "raw"
> + * - Scale is "non-linear" (perceptual)
> + * - Max brightness is 100 giving a range of 0-100 inclusive
> + * - Reading sysfs **brightness** returns the backlight device property
> + * - Reading sysfs **actual_brightness** returns the connector state value
> + * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
> + *   be used to control power.
> + * - Backlight device suspend/resume events are ignored.
> + *
> + * Note:
> + *
> + * Brightness zero should not turn off backlight it should be the minimum
> + * brightness, DPMS handles power.
> + *
> + * This function must be called from &drm_connector_funcs->late_register() since
> + * it depends on the sysfs device.
> + *
> + * Returns:
> + * Zero on success or negative error code on failure.
> + */
> +int drm_backlight_register(struct drm_connector *connector)
> +{
> +	const char *name = NULL;
> +	int ret;
> +
> +	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
> +			 connector->dev->primary->index, connector->name);
> +	if (!name)
> +		return -ENOMEM;
> +
> +	ret = drm_backlight_register_with_name(connector, name);
> +	kfree(name);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_backlight_register);
> +
> +/**
> + * drm_backlight_unregister() - Unregister backlight device
> + * @connector: Connector
> + *
> + * Unregister a backlight device. This must be called from the
> + * &drm_connector_funcs->early_unregister() callback.
> + */
> +void drm_backlight_unregister(struct drm_connector *connector)
> +{
> +	backlight_device_unregister(connector->backlight);
> +}
> +EXPORT_SYMBOL(drm_backlight_unregister);
> diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
> new file mode 100644
> index 000000000000..4151b66eb0b4
> --- /dev/null
> +++ b/include/drm/drm_backlight_helper.h
> @@ -0,0 +1,9 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> +
> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
> +
> +int drm_backlight_register(struct drm_connector *connector);
> +void drm_backlight_unregister(struct drm_connector *connector);
> +
> +#endif
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 221910948b37..ce678b694f45 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -32,6 +32,7 @@
>  
>  #include <uapi/drm/drm_mode.h>
>  
> +struct backlight_device;
>  struct drm_connector_helper_funcs;
>  struct drm_modeset_acquire_ctx;
>  struct drm_device;
> @@ -656,6 +657,12 @@ struct drm_connector_state {
>  	 */
>  	u8 max_bpc;
>  
> +	/**
> +	 * @backlight_brightness: Brightness value of the connector backlight
> +	 * device. See drm_backlight_register().
> +	 */
> +	u8 backlight_brightness;
> +
>  	/**
>  	 * @hdr_output_metadata:
>  	 * DRM blob property for HDR output metadata
> @@ -1422,6 +1429,9 @@ struct drm_connector {
>  
>  	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
>  	struct hdr_sink_metadata hdr_sink_metadata;
> +
> +	/** @backlight: Backlight device. See drm_backlight_register() */
> +	struct backlight_device *backlight;
>  };
>  
>  #define obj_to_connector(x) container_of(x, struct drm_connector, base)
> -- 
> 2.23.0
> 
> _______________________________________________
> 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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH 02/10] drm: Add backlight helper
  2020-05-04 12:06     ` Daniel Vetter
@ 2020-05-04 15:54       ` Noralf Trønnes
  -1 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-04 15:54 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, linux-usb, Hans de Goede, Daniel Thompson



Den 04.05.2020 14.06, skrev Daniel Vetter:
> On Wed, Apr 29, 2020 at 02:48:22PM +0200, Noralf Trønnes wrote:
>> This adds a function that creates a backlight device for a connector.
>> It does not deal with the KMS backlight ABI proposition[1] to add a
>> connector property. It only takes the current best practise to standardise
>> the creation of a backlight device for DRM drivers while we wait for the
>> property.
>>
>> The brightness value is set using a connector state variable and an atomic
>> commit.
>>
>> I have looked through some of the backlight users and this is what I've found:
>>
>> GNOME [2]
>> ---------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
>>
>> Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
>> Can connect connector and backlight using the sysfs device.
>>
>> KDE [3]
>> -------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
>>
>> Weston [4]
>> ----------
>>
>> Brightness range: 0-255
>> Scale: Assumes perceptual
>>
>> Chromium OS [5]
>> ---------------
>>
>> Brightness range: 0-100
>> Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)
>>
>> xserver [6]
>> -----------
>>
>> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
>> Scale: Assumes perceptual
>>
>> The builtin modesetting driver[7] does not support Backlight, Intel[8] does.
>>
>> [1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
>> [2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
>> [3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
>> [4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
>> [5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
>> [6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
>> [7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
>> [8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
>>
>> Cc: Hans de Goede <hdegoede@redhat.com>
>> Cc: Jani Nikula <jani.nikula@linux.intel.com>
>> Cc: Martin Peres <martin.peres@linux.intel.com>
>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>  Documentation/gpu/drm-kms-helpers.rst  |   6 +
>>  drivers/gpu/drm/Kconfig                |   7 ++
>>  drivers/gpu/drm/Makefile               |   1 +
>>  drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>>  include/drm/drm_backlight_helper.h     |   9 ++
>>  include/drm/drm_connector.h            |  10 ++
>>  6 files changed, 187 insertions(+)
>>  create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>>  create mode 100644 include/drm/drm_backlight_helper.h
>>
>> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
>> index 9668a7fe2408..29a2f4b49fd2 100644
>> --- a/Documentation/gpu/drm-kms-helpers.rst
>> +++ b/Documentation/gpu/drm-kms-helpers.rst
>> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>>  
>>  .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>>     :export:
>> +
>> +Backlight Helper Reference
>> +==========================
>> +
>> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
>> +   :export:
>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>> index d0aa6cff2e02..f6e13e18c9ca 100644
>> --- a/drivers/gpu/drm/Kconfig
>> +++ b/drivers/gpu/drm/Kconfig
>> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>>  	help
>>  	  Choose this if you need the GEM shmem helper functions
>>  
>> +config DRM_BACKLIGHT_HELPER
>> +	bool
>> +	depends on DRM
>> +	select BACKLIGHT_CLASS_DEVICE
>> +	help
>> +	  Choose this if you need the backlight device helper functions
>> +
>>  config DRM_VM
>>  	bool
>>  	depends on DRM && MMU
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index 6493088a0fdd..0d17662dde0a 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
>>  drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>>  drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>>  drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
>> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>>  
>>  obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>>  obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
>> diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
>> new file mode 100644
>> index 000000000000..06e6a75d1d0a
>> --- /dev/null
>> +++ b/drivers/gpu/drm/drm_backlight_helper.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0 OR MIT
>> +/*
>> + * Copyright 2020 Noralf Trønnes
>> + */
>> +
>> +#include <linux/backlight.h>
>> +
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_file.h>
>> +
>> +static int drm_backlight_update_status(struct backlight_device *bd)
>> +{
>> +	struct drm_connector *connector = bl_get_data(bd);
>> +	struct drm_connector_state *connector_state;
>> +	struct drm_device *dev = connector->dev;
>> +	struct drm_modeset_acquire_ctx ctx;
>> +	struct drm_atomic_state *state;
>> +	int ret;
>> +
>> +	state = drm_atomic_state_alloc(dev);
>> +	if (!state)
>> +		return -ENOMEM;
>> +
>> +	drm_modeset_acquire_init(&ctx, 0);
>> +	state->acquire_ctx = &ctx;
>> +retry:
>> +	connector_state = drm_atomic_get_connector_state(state, connector);
>> +	if (IS_ERR(connector_state)) {
>> +		ret = PTR_ERR(connector_state);
>> +		goto out;
>> +	}
>> +
>> +	connector_state->backlight_brightness = bd->props.brightness;
>> +
>> +	ret = drm_atomic_commit(state);
>> +out:
>> +	if (ret == -EDEADLK) {
>> +		drm_atomic_state_clear(state);
>> +		drm_modeset_backoff(&ctx);
>> +		goto retry;
>> +	}
>> +
>> +	drm_atomic_state_put(state);
>> +
>> +	drm_modeset_drop_locks(&ctx);
>> +	drm_modeset_acquire_fini(&ctx);
>> +
>> +	return ret;
>> +}
> 
> Looking at this, I'm not sure this is generally useable outside of your
> usb driver. Or stuff that does essentially the same.
> 
> For real hw drivers I expect a similar relationship between the kms driver
> and the backlight driver like for i2c, clocks, power domaines, ... i.e.
> the kms driver calls into the backlight driver. This also means that kms
> locks will be nesting outside of the locks of these subordinate
> subsystems, and hence direct access through other userspace interfaces
> (like we have for i2c too) needs to bypass atomic kms. Otherwise we have
> deadlock potential.
> 
> 
> With your stuff here we can potentially get a backlight locks -> kms locks
> -> backlight locks scenario, and lockdep will be angry about this. So in
> general this doesn't work (I think, locking is hard), and in this specific
> case it only works because your usb driver shovels everything over to
> another machine. Lockdep will still complain if you load this together
> with some other kms driver that uses backlight normally.
> 
> Aside: Locking is broken in the backlight code, we take the
> bd->update_lock too late, after bd->props has already been handled
> unsafely. But I guess that's a side effect of the sysfs interface being
> racy by default. Or the backlight_enable/disable helpers should take
> bd->ops_lock too.
> 
> So two things:
> - I think the actual update needs to be pushed to a work_struct, with no
>   flush_work, to avoid these locking loops completely.
> - I think this is best left in your usb driver for now, until someone
>   comes up with maybe an spi forwarder or whatever where we might need
>   this again.
> 
> Or, and this is probably much simpler, just have a simpler backlight
> driver that's completely orthogonal to the kms side, with some hw lock to
> organize updates. For that case just adding a drm_connector->backlight
> pointer, as prep for Hans' work, would be great.

I don't quite follow you here. This backlight device is for kms drivers
that control the backlight brightness by themselves, by setting a pwm
value or something on their hardware. The kms driver will not act as a
proxy and forward the value to another backlight driver. Nor will
another driver use this backlight device. It's purely for userspace. So
I don't understand how a locking loop can happen.

What this code does for me is pushing brightness changes through the
atomic machinery so I can treat it like any other property change.
So the reason I made it a core helper is that I figured I wasn't the
only one who would want this.

I'll move it inside my driver unless someone chimes in and say they want
it to.

Noralf.

> -Daniel
> 
> 
>> +
>> +static int drm_backlight_get_brightness(struct backlight_device *bd)
>> +{
>> +	struct drm_connector *connector = bl_get_data(bd);
>> +	struct drm_device *dev = connector->dev;
>> +	int brightness;
>> +
>> +	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
>> +	brightness = connector->state->backlight_brightness;
>> +	drm_modeset_unlock(&dev->mode_config.connection_mutex);
>> +
>> +	return brightness;
>> +}
>> +
>> +static const struct backlight_ops drm_backlight_ops = {
>> +	.get_brightness = drm_backlight_get_brightness,
>> +	.update_status	= drm_backlight_update_status,
>> +};
>> +
>> +/* Can be exported for drivers carrying a legacy name */
>> +static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
>> +{
>> +	struct backlight_device *bd;
>> +	const struct backlight_properties props = {
>> +		.type = BACKLIGHT_RAW,
>> +		.scale = BACKLIGHT_SCALE_NON_LINEAR,
>> +		.max_brightness = 100,
>> +	};
>> +
>> +	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
>> +		    !drm_drv_uses_atomic_modeset(connector->dev) ||
>> +		    !connector->kdev))
>> +		return -EINVAL;
>> +
>> +	bd = backlight_device_register(name, connector->kdev, connector,
>> +				       &drm_backlight_ops, &props);
>> +	if (IS_ERR(bd))
>> +		return PTR_ERR(bd);
>> +
>> +	connector->backlight = bd;
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * drm_backlight_register() - Register a backlight device for a connector
>> + * @connector: Connector
>> + *
>> + * This function registers a backlight device for @connector with the following
>> + * characteristics:
>> + *
>> + * - The connector sysfs device is used as a parent device for the backlight device.
>> + *   Userspace can use this to connect backlight device and connector.
>> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
>> + * - Type is "raw"
>> + * - Scale is "non-linear" (perceptual)
>> + * - Max brightness is 100 giving a range of 0-100 inclusive
>> + * - Reading sysfs **brightness** returns the backlight device property
>> + * - Reading sysfs **actual_brightness** returns the connector state value
>> + * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
>> + *   be used to control power.
>> + * - Backlight device suspend/resume events are ignored.
>> + *
>> + * Note:
>> + *
>> + * Brightness zero should not turn off backlight it should be the minimum
>> + * brightness, DPMS handles power.
>> + *
>> + * This function must be called from &drm_connector_funcs->late_register() since
>> + * it depends on the sysfs device.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_backlight_register(struct drm_connector *connector)
>> +{
>> +	const char *name = NULL;
>> +	int ret;
>> +
>> +	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
>> +			 connector->dev->primary->index, connector->name);
>> +	if (!name)
>> +		return -ENOMEM;
>> +
>> +	ret = drm_backlight_register_with_name(connector, name);
>> +	kfree(name);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_backlight_register);
>> +
>> +/**
>> + * drm_backlight_unregister() - Unregister backlight device
>> + * @connector: Connector
>> + *
>> + * Unregister a backlight device. This must be called from the
>> + * &drm_connector_funcs->early_unregister() callback.
>> + */
>> +void drm_backlight_unregister(struct drm_connector *connector)
>> +{
>> +	backlight_device_unregister(connector->backlight);
>> +}
>> +EXPORT_SYMBOL(drm_backlight_unregister);
>> diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
>> new file mode 100644
>> index 000000000000..4151b66eb0b4
>> --- /dev/null
>> +++ b/include/drm/drm_backlight_helper.h
>> @@ -0,0 +1,9 @@
>> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
>> +
>> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
>> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
>> +
>> +int drm_backlight_register(struct drm_connector *connector);
>> +void drm_backlight_unregister(struct drm_connector *connector);
>> +
>> +#endif
>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>> index 221910948b37..ce678b694f45 100644
>> --- a/include/drm/drm_connector.h
>> +++ b/include/drm/drm_connector.h
>> @@ -32,6 +32,7 @@
>>  
>>  #include <uapi/drm/drm_mode.h>
>>  
>> +struct backlight_device;
>>  struct drm_connector_helper_funcs;
>>  struct drm_modeset_acquire_ctx;
>>  struct drm_device;
>> @@ -656,6 +657,12 @@ struct drm_connector_state {
>>  	 */
>>  	u8 max_bpc;
>>  
>> +	/**
>> +	 * @backlight_brightness: Brightness value of the connector backlight
>> +	 * device. See drm_backlight_register().
>> +	 */
>> +	u8 backlight_brightness;
>> +
>>  	/**
>>  	 * @hdr_output_metadata:
>>  	 * DRM blob property for HDR output metadata
>> @@ -1422,6 +1429,9 @@ struct drm_connector {
>>  
>>  	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
>>  	struct hdr_sink_metadata hdr_sink_metadata;
>> +
>> +	/** @backlight: Backlight device. See drm_backlight_register() */
>> +	struct backlight_device *backlight;
>>  };
>>  
>>  #define obj_to_connector(x) container_of(x, struct drm_connector, base)
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 

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

* Re: [PATCH 02/10] drm: Add backlight helper
@ 2020-05-04 15:54       ` Noralf Trønnes
  0 siblings, 0 replies; 66+ messages in thread
From: Noralf Trønnes @ 2020-05-04 15:54 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: Hans de Goede, Daniel Thompson, linux-usb, dri-devel



Den 04.05.2020 14.06, skrev Daniel Vetter:
> On Wed, Apr 29, 2020 at 02:48:22PM +0200, Noralf Trønnes wrote:
>> This adds a function that creates a backlight device for a connector.
>> It does not deal with the KMS backlight ABI proposition[1] to add a
>> connector property. It only takes the current best practise to standardise
>> the creation of a backlight device for DRM drivers while we wait for the
>> property.
>>
>> The brightness value is set using a connector state variable and an atomic
>> commit.
>>
>> I have looked through some of the backlight users and this is what I've found:
>>
>> GNOME [2]
>> ---------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
>>
>> Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
>> Can connect connector and backlight using the sysfs device.
>>
>> KDE [3]
>> -------
>>
>> Brightness range: 0-100
>> Scale: Assumes perceptual
>>
>> Weston [4]
>> ----------
>>
>> Brightness range: 0-255
>> Scale: Assumes perceptual
>>
>> Chromium OS [5]
>> ---------------
>>
>> Brightness range: 0-100
>> Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)
>>
>> xserver [6]
>> -----------
>>
>> Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
>> Scale: Assumes perceptual
>>
>> The builtin modesetting driver[7] does not support Backlight, Intel[8] does.
>>
>> [1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
>> [2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
>> [3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
>> [4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
>> [5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
>> [6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
>> [7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
>> [8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c
>>
>> Cc: Hans de Goede <hdegoede@redhat.com>
>> Cc: Jani Nikula <jani.nikula@linux.intel.com>
>> Cc: Martin Peres <martin.peres@linux.intel.com>
>> Cc: Daniel Thompson <daniel.thompson@linaro.org>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>  Documentation/gpu/drm-kms-helpers.rst  |   6 +
>>  drivers/gpu/drm/Kconfig                |   7 ++
>>  drivers/gpu/drm/Makefile               |   1 +
>>  drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
>>  include/drm/drm_backlight_helper.h     |   9 ++
>>  include/drm/drm_connector.h            |  10 ++
>>  6 files changed, 187 insertions(+)
>>  create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
>>  create mode 100644 include/drm/drm_backlight_helper.h
>>
>> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
>> index 9668a7fe2408..29a2f4b49fd2 100644
>> --- a/Documentation/gpu/drm-kms-helpers.rst
>> +++ b/Documentation/gpu/drm-kms-helpers.rst
>> @@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
>>  
>>  .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
>>     :export:
>> +
>> +Backlight Helper Reference
>> +==========================
>> +
>> +.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
>> +   :export:
>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>> index d0aa6cff2e02..f6e13e18c9ca 100644
>> --- a/drivers/gpu/drm/Kconfig
>> +++ b/drivers/gpu/drm/Kconfig
>> @@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
>>  	help
>>  	  Choose this if you need the GEM shmem helper functions
>>  
>> +config DRM_BACKLIGHT_HELPER
>> +	bool
>> +	depends on DRM
>> +	select BACKLIGHT_CLASS_DEVICE
>> +	help
>> +	  Choose this if you need the backlight device helper functions
>> +
>>  config DRM_VM
>>  	bool
>>  	depends on DRM && MMU
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index 6493088a0fdd..0d17662dde0a 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
>>  drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>>  drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
>>  drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
>> +drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
>>  
>>  obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
>>  obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
>> diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
>> new file mode 100644
>> index 000000000000..06e6a75d1d0a
>> --- /dev/null
>> +++ b/drivers/gpu/drm/drm_backlight_helper.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0 OR MIT
>> +/*
>> + * Copyright 2020 Noralf Trønnes
>> + */
>> +
>> +#include <linux/backlight.h>
>> +
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_file.h>
>> +
>> +static int drm_backlight_update_status(struct backlight_device *bd)
>> +{
>> +	struct drm_connector *connector = bl_get_data(bd);
>> +	struct drm_connector_state *connector_state;
>> +	struct drm_device *dev = connector->dev;
>> +	struct drm_modeset_acquire_ctx ctx;
>> +	struct drm_atomic_state *state;
>> +	int ret;
>> +
>> +	state = drm_atomic_state_alloc(dev);
>> +	if (!state)
>> +		return -ENOMEM;
>> +
>> +	drm_modeset_acquire_init(&ctx, 0);
>> +	state->acquire_ctx = &ctx;
>> +retry:
>> +	connector_state = drm_atomic_get_connector_state(state, connector);
>> +	if (IS_ERR(connector_state)) {
>> +		ret = PTR_ERR(connector_state);
>> +		goto out;
>> +	}
>> +
>> +	connector_state->backlight_brightness = bd->props.brightness;
>> +
>> +	ret = drm_atomic_commit(state);
>> +out:
>> +	if (ret == -EDEADLK) {
>> +		drm_atomic_state_clear(state);
>> +		drm_modeset_backoff(&ctx);
>> +		goto retry;
>> +	}
>> +
>> +	drm_atomic_state_put(state);
>> +
>> +	drm_modeset_drop_locks(&ctx);
>> +	drm_modeset_acquire_fini(&ctx);
>> +
>> +	return ret;
>> +}
> 
> Looking at this, I'm not sure this is generally useable outside of your
> usb driver. Or stuff that does essentially the same.
> 
> For real hw drivers I expect a similar relationship between the kms driver
> and the backlight driver like for i2c, clocks, power domaines, ... i.e.
> the kms driver calls into the backlight driver. This also means that kms
> locks will be nesting outside of the locks of these subordinate
> subsystems, and hence direct access through other userspace interfaces
> (like we have for i2c too) needs to bypass atomic kms. Otherwise we have
> deadlock potential.
> 
> 
> With your stuff here we can potentially get a backlight locks -> kms locks
> -> backlight locks scenario, and lockdep will be angry about this. So in
> general this doesn't work (I think, locking is hard), and in this specific
> case it only works because your usb driver shovels everything over to
> another machine. Lockdep will still complain if you load this together
> with some other kms driver that uses backlight normally.
> 
> Aside: Locking is broken in the backlight code, we take the
> bd->update_lock too late, after bd->props has already been handled
> unsafely. But I guess that's a side effect of the sysfs interface being
> racy by default. Or the backlight_enable/disable helpers should take
> bd->ops_lock too.
> 
> So two things:
> - I think the actual update needs to be pushed to a work_struct, with no
>   flush_work, to avoid these locking loops completely.
> - I think this is best left in your usb driver for now, until someone
>   comes up with maybe an spi forwarder or whatever where we might need
>   this again.
> 
> Or, and this is probably much simpler, just have a simpler backlight
> driver that's completely orthogonal to the kms side, with some hw lock to
> organize updates. For that case just adding a drm_connector->backlight
> pointer, as prep for Hans' work, would be great.

I don't quite follow you here. This backlight device is for kms drivers
that control the backlight brightness by themselves, by setting a pwm
value or something on their hardware. The kms driver will not act as a
proxy and forward the value to another backlight driver. Nor will
another driver use this backlight device. It's purely for userspace. So
I don't understand how a locking loop can happen.

What this code does for me is pushing brightness changes through the
atomic machinery so I can treat it like any other property change.
So the reason I made it a core helper is that I figured I wasn't the
only one who would want this.

I'll move it inside my driver unless someone chimes in and say they want
it to.

Noralf.

> -Daniel
> 
> 
>> +
>> +static int drm_backlight_get_brightness(struct backlight_device *bd)
>> +{
>> +	struct drm_connector *connector = bl_get_data(bd);
>> +	struct drm_device *dev = connector->dev;
>> +	int brightness;
>> +
>> +	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
>> +	brightness = connector->state->backlight_brightness;
>> +	drm_modeset_unlock(&dev->mode_config.connection_mutex);
>> +
>> +	return brightness;
>> +}
>> +
>> +static const struct backlight_ops drm_backlight_ops = {
>> +	.get_brightness = drm_backlight_get_brightness,
>> +	.update_status	= drm_backlight_update_status,
>> +};
>> +
>> +/* Can be exported for drivers carrying a legacy name */
>> +static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
>> +{
>> +	struct backlight_device *bd;
>> +	const struct backlight_properties props = {
>> +		.type = BACKLIGHT_RAW,
>> +		.scale = BACKLIGHT_SCALE_NON_LINEAR,
>> +		.max_brightness = 100,
>> +	};
>> +
>> +	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
>> +		    !drm_drv_uses_atomic_modeset(connector->dev) ||
>> +		    !connector->kdev))
>> +		return -EINVAL;
>> +
>> +	bd = backlight_device_register(name, connector->kdev, connector,
>> +				       &drm_backlight_ops, &props);
>> +	if (IS_ERR(bd))
>> +		return PTR_ERR(bd);
>> +
>> +	connector->backlight = bd;
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * drm_backlight_register() - Register a backlight device for a connector
>> + * @connector: Connector
>> + *
>> + * This function registers a backlight device for @connector with the following
>> + * characteristics:
>> + *
>> + * - The connector sysfs device is used as a parent device for the backlight device.
>> + *   Userspace can use this to connect backlight device and connector.
>> + * - Name will be on the form: **card0-HDMI-A-1-backlight**
>> + * - Type is "raw"
>> + * - Scale is "non-linear" (perceptual)
>> + * - Max brightness is 100 giving a range of 0-100 inclusive
>> + * - Reading sysfs **brightness** returns the backlight device property
>> + * - Reading sysfs **actual_brightness** returns the connector state value
>> + * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
>> + *   be used to control power.
>> + * - Backlight device suspend/resume events are ignored.
>> + *
>> + * Note:
>> + *
>> + * Brightness zero should not turn off backlight it should be the minimum
>> + * brightness, DPMS handles power.
>> + *
>> + * This function must be called from &drm_connector_funcs->late_register() since
>> + * it depends on the sysfs device.
>> + *
>> + * Returns:
>> + * Zero on success or negative error code on failure.
>> + */
>> +int drm_backlight_register(struct drm_connector *connector)
>> +{
>> +	const char *name = NULL;
>> +	int ret;
>> +
>> +	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
>> +			 connector->dev->primary->index, connector->name);
>> +	if (!name)
>> +		return -ENOMEM;
>> +
>> +	ret = drm_backlight_register_with_name(connector, name);
>> +	kfree(name);
>> +
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL(drm_backlight_register);
>> +
>> +/**
>> + * drm_backlight_unregister() - Unregister backlight device
>> + * @connector: Connector
>> + *
>> + * Unregister a backlight device. This must be called from the
>> + * &drm_connector_funcs->early_unregister() callback.
>> + */
>> +void drm_backlight_unregister(struct drm_connector *connector)
>> +{
>> +	backlight_device_unregister(connector->backlight);
>> +}
>> +EXPORT_SYMBOL(drm_backlight_unregister);
>> diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
>> new file mode 100644
>> index 000000000000..4151b66eb0b4
>> --- /dev/null
>> +++ b/include/drm/drm_backlight_helper.h
>> @@ -0,0 +1,9 @@
>> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
>> +
>> +#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
>> +#define _LINUX_DRM_BACKLIGHT_HELPER_H
>> +
>> +int drm_backlight_register(struct drm_connector *connector);
>> +void drm_backlight_unregister(struct drm_connector *connector);
>> +
>> +#endif
>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>> index 221910948b37..ce678b694f45 100644
>> --- a/include/drm/drm_connector.h
>> +++ b/include/drm/drm_connector.h
>> @@ -32,6 +32,7 @@
>>  
>>  #include <uapi/drm/drm_mode.h>
>>  
>> +struct backlight_device;
>>  struct drm_connector_helper_funcs;
>>  struct drm_modeset_acquire_ctx;
>>  struct drm_device;
>> @@ -656,6 +657,12 @@ struct drm_connector_state {
>>  	 */
>>  	u8 max_bpc;
>>  
>> +	/**
>> +	 * @backlight_brightness: Brightness value of the connector backlight
>> +	 * device. See drm_backlight_register().
>> +	 */
>> +	u8 backlight_brightness;
>> +
>>  	/**
>>  	 * @hdr_output_metadata:
>>  	 * DRM blob property for HDR output metadata
>> @@ -1422,6 +1429,9 @@ struct drm_connector {
>>  
>>  	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
>>  	struct hdr_sink_metadata hdr_sink_metadata;
>> +
>> +	/** @backlight: Backlight device. See drm_backlight_register() */
>> +	struct backlight_device *backlight;
>>  };
>>  
>>  #define obj_to_connector(x) container_of(x, struct drm_connector, base)
>> -- 
>> 2.23.0
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
> 
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

end of thread, other threads:[~2020-05-04 15:54 UTC | newest]

Thread overview: 66+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-04-29 12:48 [PATCH 00/10] Generic USB Display driver Noralf Trønnes
2020-04-29 12:48 ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 01/10] backlight: Add backlight_device_get_by_name() Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-04-30  8:32   ` Lee Jones
2020-04-30  8:32     ` Lee Jones
2020-04-30  9:20     ` Noralf Trønnes
2020-04-30  9:20       ` Noralf Trønnes
2020-04-30 10:15       ` Lee Jones
2020-04-30 10:15         ` Lee Jones
2020-04-30 10:42         ` Noralf Trønnes
2020-04-30 10:42           ` Noralf Trønnes
2020-04-30 14:02         ` Daniel Vetter
2020-04-30 14:02           ` Daniel Vetter
2020-05-04  7:10           ` Lee Jones
2020-05-04  7:10             ` Lee Jones
2020-05-03  7:13   ` Sam Ravnborg
2020-05-03  7:13     ` Sam Ravnborg
2020-04-29 12:48 ` [PATCH 02/10] drm: Add backlight helper Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-04-29 14:13   ` Hans de Goede
2020-04-29 14:13     ` Hans de Goede
2020-04-29 18:40     ` Noralf Trønnes
2020-04-29 18:40       ` Noralf Trønnes
2020-04-30 15:36       ` Hans de Goede
2020-04-30 15:36         ` Hans de Goede
2020-05-04 12:06   ` Daniel Vetter
2020-05-04 12:06     ` Daniel Vetter
2020-05-04 15:54     ` Noralf Trønnes
2020-05-04 15:54       ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 03/10] drm/client: Add drm_client_init_from_id() Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 04/10] drm/client: Add drm_client_framebuffer_flush() Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-05-03  7:48   ` Sam Ravnborg
2020-05-03  7:48     ` Sam Ravnborg
2020-05-03  9:54     ` Noralf Trønnes
2020-05-03  9:54       ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 05/10] drm/client: Add drm_client_modeset_check() Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-05-03  8:03   ` Sam Ravnborg
2020-05-03  8:03     ` Sam Ravnborg
2020-05-03 10:02     ` Noralf Trønnes
2020-05-03 10:02       ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 06/10] drm/client: Add a way to set modeset, properties and rotation Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-05-03  8:47   ` Sam Ravnborg
2020-05-03  8:47     ` Sam Ravnborg
2020-05-03 10:50     ` Noralf Trønnes
2020-05-03 10:50       ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 07/10] drm/format-helper: Add drm_fb_swab() Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-05-03 10:29   ` Sam Ravnborg
2020-05-03 10:29     ` Sam Ravnborg
2020-05-03 13:38     ` Noralf Trønnes
2020-05-03 13:38       ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 08/10] drm: Add Generic USB Display driver Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-05-02 17:58   ` Noralf Trønnes
2020-05-02 17:58     ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 09/10] drm/gud: Add functionality for the USB gadget side Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-04-29 12:48 ` [PATCH 10/10] usb: gadget: function: Add Generic USB Display support Noralf Trønnes
2020-04-29 12:48   ` Noralf Trønnes
2020-05-01 13:22 ` [PATCH 00/10] Generic USB Display driver Noralf Trønnes
2020-05-01 13:22   ` Noralf Trønnes

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.