Add display abstraction and helpers to probe for displays and commit
modesets.

TODO:
If the bootsplash client doesn't need to subclass drm_client_display,
the callbacks can be removed.

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

diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index 3bc96b0b30ec..ef01a31a9dbe 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -4,6 +4,7 @@
  */
 
 #include <linux/list.h>
+#include <linux/list_sort.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/seq_file.h>
@@ -106,6 +107,9 @@ int drm_client_init(struct drm_device *dev, struct 
drm_client_dev *client,
 
        drm_dev_get(dev);
 
+       mutex_init(&client->displaylist_mutex);
+       INIT_LIST_HEAD(&client->displaylist);
+
        return 0;
 
 err_put_module:
@@ -156,6 +160,9 @@ void drm_client_release(struct drm_client_dev *client)
 
        DRM_DEV_DEBUG_KMS(dev->dev, "%s\n", client->name);
 
+       drm_client_release_displays(client);
+       mutex_destroy(&client->displaylist_mutex);
+
        drm_client_close(client);
        drm_dev_put(dev);
        if (client->funcs)
@@ -1419,6 +1426,414 @@ void drm_client_modesets_dpms(struct drm_device *dev, 
struct drm_mode_set *modes
 }
 EXPORT_SYMBOL(drm_client_modesets_dpms);
 
+static struct drm_client_display *
+drm_client_display_alloc(struct drm_client_dev *client, unsigned int 
num_modesets)
+{
+       struct drm_client_display *display;
+       struct drm_mode_set *modesets;
+
+       modesets = kcalloc(num_modesets + 1, sizeof(*modesets), GFP_KERNEL);
+       if (!modesets)
+               return ERR_PTR(-ENOMEM);
+
+       if (client->funcs && client->funcs->display_alloc)
+               display = client->funcs->display_alloc(client);
+       else
+               display = kzalloc(sizeof(*display), GFP_KERNEL);
+       if (!display)
+               display = ERR_PTR(-ENOMEM);
+
+       if (IS_ERR(display)) {
+               kfree(modesets);
+               return display;
+       }
+
+       display->client = client;
+       display->modesets = modesets;
+
+       return display;
+}
+
+static void drm_client_display_release(struct drm_client_display *display)
+{
+       unsigned int i;
+
+       if (!display)
+               return;
+
+       for (i = 0; i < display->num_buffers; i++)
+               drm_client_framebuffer_delete(display->buffers[i]);
+       kfree(display->buffers);
+
+       drm_mode_destroy(display->client->dev, display->mode);
+
+       drm_client_modesets_release(display->modesets);
+
+       if (display->client->funcs && display->client->funcs->display_free)
+               display->client->funcs->display_free(display);
+       else
+               kfree(display);
+}
+
+static void drm_client_display_debugprint(struct drm_client_display *display)
+{
+       struct drm_display_mode *mode = display->mode;
+       struct drm_mode_set *modeset;
+       unsigned int i;
+
+       DRM_DEBUG_KMS("  %dx%d %dHz\n", mode->hdisplay, mode->vdisplay, 
mode->vrefresh);
+
+       drm_client_for_each_modeset(modeset, display->modesets) {
+               DRM_DEBUG_KMS("    crtc=%d, connectors:", 
modeset->crtc->base.id);
+               for (i = 0; i < modeset->num_connectors; i++)
+                       DRM_DEBUG_KMS("      %s\n", 
modeset->connectors[i]->name);
+       }
+}
+
+static bool drm_client_modeset_equal(struct drm_mode_set *modeset1, struct 
drm_mode_set *modeset2)
+{
+       unsigned int i;
+
+       if (modeset1->crtc != modeset2->crtc ||
+           !drm_mode_equal(modeset1->mode, modeset2->mode) ||
+           modeset1->x != modeset2->x || modeset1->y != modeset2->y ||
+           modeset1->num_connectors != modeset2->num_connectors)
+               return false;
+
+       for (i = 0; i < modeset1->num_connectors; i++) {
+               if (modeset1->connectors[i] != modeset2->connectors[i])
+                       return false;
+       }
+
+       return true;
+}
+
+static bool drm_client_display_equal(struct drm_client_display *display1,
+                                    struct drm_client_display *display2)
+{
+       struct drm_mode_set *modeset1, *modeset2;
+
+       if (!display1 || !display2)
+               return false;
+
+       if (display1 == display2)
+               return true;
+
+       if (!drm_mode_equal(display1->mode, display2->mode))
+               return false;
+
+       for (modeset1 = display1->modesets, modeset2 = display2->modesets;
+            modeset1->crtc && modeset2->crtc; modeset1++, modeset2++)
+               if (!drm_client_modeset_equal(modeset1, modeset2))
+                       return false;
+
+       return !modeset1->crtc && !modeset2->crtc;
+}
+
+static int drm_client_display_framebuffer_create(struct drm_client_display 
*display,
+                                                unsigned int num_buffers, u32 
format)
+{
+       struct drm_client_buffer **buffers;
+       struct drm_mode_set *modeset;
+       unsigned int i;
+       int ret;
+
+       if (!display || !display->mode)
+               return -EINVAL;
+
+       if (display->num_buffers && display->num_buffers != num_buffers)
+               return -EINVAL;
+
+       if (display->num_buffers == num_buffers)
+               return 0;
+
+       buffers = kcalloc(num_buffers, sizeof(*buffers), GFP_KERNEL);
+       if (!buffers)
+               return -ENOMEM;
+
+       for (i = 0; i < num_buffers; i++) {
+               buffers[i] = drm_client_framebuffer_create(display->client, 
display->mode->hdisplay,
+                                                          
display->mode->vdisplay, format);
+               if (IS_ERR(buffers[i])) {
+                       ret = PTR_ERR(buffers[i]);
+                       goto err_free;
+               }
+       }
+
+       display->buffers = buffers;
+       display->num_buffers = num_buffers;
+
+       drm_client_for_each_modeset(modeset, display->modesets)
+               modeset->fb = display->buffers[0]->fb;
+
+       return 0;
+
+err_free:
+       for (i = 0; i < num_buffers; i++) {
+               if (!IS_ERR_OR_NULL(buffers[i]))
+                       drm_client_framebuffer_delete(buffers[i]);
+       }
+       kfree(buffers);
+
+       return ret;
+}
+
+static int drm_client_find_displays(struct drm_client_dev *client, struct 
list_head *displaylist)
+{
+       struct drm_mode_set *modeset, *modesets;
+       struct drm_device *dev = client->dev;
+       struct drm_client_display *display;
+       unsigned int num_modesets = 0;
+       int ret = 0;
+
+       modesets = drm_client_modesets_probe(dev, 0, 0);
+       if (IS_ERR_OR_NULL(modesets))
+               return PTR_ERR_OR_ZERO(modesets);
+
+       /* TODO: Support more than one tiled monitor? */
+       display = NULL;
+       drm_client_for_each_modeset(modeset, modesets) {
+               if (!modeset->mode || !modeset->connectors[0]->has_tile)
+                       continue;
+
+               if (!display) {
+                       display = drm_client_display_alloc(client, 
dev->mode_config.num_crtc);
+                       if (IS_ERR(display)) {
+                               ret = PTR_ERR(display);
+                               goto err_free;
+                       }
+
+                       list_add(&display->list, displaylist);
+               }
+
+               display->modesets[num_modesets++] = *modeset;
+               modeset->num_connectors = 0;
+               modeset->connectors = NULL;
+               modeset->mode = NULL;
+       }
+
+       /* Contruct a mode for the tiled monitor */
+       if (display) {
+               int hdisplay = 0, vdisplay = 0, vrefresh = 0;
+
+               drm_client_for_each_modeset(modeset, display->modesets) {
+                       if (!modeset->y)
+                               hdisplay += modeset->mode->hdisplay;
+                       if (!modeset->x)
+                               vdisplay += modeset->mode->vdisplay;
+                       vrefresh = modeset->mode->vrefresh;
+               }
+
+               display->mode = drm_cvt_mode(dev, hdisplay, vdisplay, vrefresh, 
false, false, false);
+               if (!display->mode) {
+                       ret = -ENOMEM;
+                       goto err_free;
+               }
+       }
+
+       /* The rest have one display per modeset */
+       drm_client_for_each_modeset(modeset, modesets) {
+               if (!modeset->mode || modeset->connectors[0]->has_tile)
+                       continue;
+
+               display = drm_client_display_alloc(client, 1);
+               if (IS_ERR(display)) {
+                       ret = PTR_ERR(display);
+                       goto err_free;
+               }
+
+               list_add(&display->list, displaylist);
+               display->modesets[0] = *modeset;
+               modeset->num_connectors = 0;
+               modeset->connectors = NULL;
+               modeset->mode = NULL;
+
+               display->mode = drm_mode_duplicate(dev, 
display->modesets[0].mode);
+               if (!display->mode) {
+                       ret = -ENOMEM;
+                       goto err_free;
+               }
+       }
+
+       goto out;
+
+err_free:
+       list_for_each_entry(display, displaylist, list)
+               drm_client_display_release(display);
+out:
+       drm_client_modesets_release(modesets);
+
+       return ret;
+}
+
+static int drm_client_displays_compare(void *priv, struct list_head *lh_a, 
struct list_head *lh_b)
+{
+       struct drm_client_display *a = list_entry(lh_a, struct 
drm_client_display, list);
+       struct drm_client_display *b = list_entry(lh_b, struct 
drm_client_display, list);
+
+       return b->mode->hdisplay * b->mode->vdisplay - a->mode->hdisplay * 
a->mode->vdisplay;
+}
+
+static void drm_client_displays_sort(struct list_head *displaylist)
+{
+       list_sort(NULL, displaylist, drm_client_displays_compare);
+}
+
+/**
+ * drm_client_probe_displays() - Probe for displays
+ * @client: DRM client
+ * @num_buffers: Number of buffers to attach (optional)
+ * @format: Buffer format
+ *
+ * This function probes for connected displays and updates the clients list of 
displays.
+ * The list is sorted from largest to smallest.
+ *
+ * Returns:
+ * Number of displays or negative error code on failure.
+ */
+int drm_client_probe_displays(struct drm_client_dev *client, unsigned int 
num_buffers, u32 format)
+{
+       struct drm_client_display *display, *tmp;
+       LIST_HEAD(displaylist);
+       bool changed = false;
+       int ret;
+
+       ret = drm_client_find_displays(client, &displaylist);
+       if (ret < 0)
+               return ret;
+
+       if (list_empty(&displaylist)) {
+               drm_client_release_displays(client);
+               return 0;
+       }
+
+       mutex_lock(&client->displaylist_mutex);
+
+       /* If a display hasn't changed, keep it to avoid reallocating buffers */
+       list_for_each_entry_safe(display, tmp, &client->displaylist, list) {
+               struct drm_client_display *display2, *tmp2;
+               bool found = false;
+
+               list_for_each_entry_safe(display2, tmp2, &displaylist, list) {
+                       if (drm_client_display_equal(display, display2)) {
+                               list_del(&display2->list);
+                               drm_client_display_release(display2);
+                               found = true;
+                               break;
+                       }
+               }
+
+               if (!found) {
+                       list_del(&display->list);
+                       drm_client_display_release(display);
+                       changed = true;
+               }
+       }
+
+       if (!list_empty(&displaylist))
+               changed = true;
+
+       list_splice(&displaylist, &client->displaylist);
+
+       /* Sort from largest to smallest */
+       drm_client_displays_sort(&client->displaylist);
+
+       if (changed) {
+               DRM_DEBUG_KMS("Displays:\n");
+               drm_client_for_each_display(display, client)
+                       drm_client_display_debugprint(display);
+       }
+
+       if (num_buffers) {
+               drm_client_for_each_display(display, client) {
+                       ret = drm_client_display_framebuffer_create(display, 
num_buffers, format);
+                       if (ret)
+                               goto out_unlock;
+               }
+       }
+
+       ret = 0;
+       drm_client_for_each_display(display, client)
+               ret++;
+
+out_unlock:
+       mutex_unlock(&client->displaylist_mutex);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_client_probe_displays);
+
+/**
+ * drm_client_release_displays() - Release displays
+ * @client: DRM client
+ *
+ * This function releases all the clients displays.
+ */
+void drm_client_release_displays(struct drm_client_dev *client)
+{
+       struct drm_client_display *display, *tmp;
+
+       mutex_lock(&client->displaylist_mutex);
+       list_for_each_entry_safe(display, tmp, &client->displaylist, list) {
+               list_del(&display->list);
+               drm_client_display_release(display);
+       }
+       mutex_unlock(&client->displaylist_mutex);
+}
+EXPORT_SYMBOL(drm_client_release_displays);
+
+static int drm_client_display_set_buffer(struct drm_client_display *display, 
unsigned int buffer)
+{
+       struct drm_mode_set *modeset;
+
+       if (!display || buffer >= display->num_buffers)
+               return -EINVAL;
+
+       drm_client_for_each_modeset(modeset, display->modesets)
+               modeset->fb = display->buffers[buffer]->fb;
+
+       return 0;
+}
+
+static int drm_client_display_commit(struct drm_client_display *display)
+{
+       int ret;
+
+       if (!display)
+               return -EINVAL;
+
+       if (!drm_master_internal_acquire(display->client->dev))
+               return -EBUSY;
+
+       ret = drm_client_modesets_commit(display->client->dev, 
display->modesets);
+
+       drm_master_internal_release(display->client->dev);
+
+       return ret;
+}
+
+/**
+ * drm_client_display_commit_buffer() - Commit display modeset(s) with buffer
+ * @display: Client display
+ * @buffer: Buffer index
+ *
+ * This function sets the framebuffer to @buffer and commits the modeset(s).
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_display_commit_buffer(struct drm_client_display *display, 
unsigned int buffer)
+{
+       int ret;
+
+       ret = drm_client_display_set_buffer(display, buffer);
+       if (ret)
+               return ret;
+
+       return drm_client_display_commit(display);
+}
+EXPORT_SYMBOL(drm_client_display_commit_buffer);
+
 #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 78fb82bd8371..ef7a9bd07b3c 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -3,12 +3,15 @@
 #ifndef _DRM_CLIENT_H_
 #define _DRM_CLIENT_H_
 
+#include <linux/list.h>
+#include <linux/mutex.h>
 #include <linux/types.h>
 
 #include <drm/drm_connector.h>
 #include <drm/drm_crtc.h>
 
 struct drm_client_dev;
+struct drm_client_display;
 struct drm_device;
 struct drm_file;
 struct drm_framebuffer;
@@ -55,6 +58,25 @@ struct drm_client_funcs {
         * This callback is optional.
         */
        int (*hotplug)(struct drm_client_dev *client);
+
+       /**
+        * @display_alloc:
+        *
+        * Called when allocating a &drm_client_display. It can be use to
+        * subclass the structure.
+        *
+        * This callback is optional.
+        */
+       struct drm_client_display *(*display_alloc)(struct drm_client_dev 
*client);
+
+       /**
+        * @display_free:
+        *
+        * Called when releasing a &drm_client_display.
+        *
+        * This callback is optional.
+        */
+       void (*display_free)(struct drm_client_display *display);
 };
 
 /**
@@ -88,6 +110,19 @@ struct drm_client_dev {
         * @file: DRM file
         */
        struct drm_file *file;
+
+       /**
+        * @displaylist_mutex: Protects @displaylist.
+        */
+       struct mutex displaylist_mutex;
+
+       /**
+        * @displaylist:
+        *
+        * List of displays, linked through &drm_client_display.list.
+        */
+       struct list_head displaylist;
+
 };
 
 int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
@@ -169,6 +204,51 @@ void drm_client_modesets_dpms(struct drm_device *dev, 
struct drm_mode_set *modes
 #define drm_client_for_each_modeset(modeset, modesets) \
        for (modeset = modesets; modeset->crtc; modeset++)
 
+/**
+ * struct drm_client_display - DRM client display
+ */
+struct drm_client_display {
+       /**
+        * @client: DRM client.
+        */
+       struct drm_client_dev *client;
+
+       /**
+        * @list:
+        *
+        * List of all displays for a client, linked into
+        * &drm_client_dev.displaylist. Protected by 
&drm_client_dev.displaylist_mutex.
+        */
+       struct list_head list;
+
+       /**
+        * @mode: Current display mode.
+        */
+       struct drm_display_mode *mode;
+
+       /**
+        * @modesets: Per CRTC array of modeset configurations.
+        */
+       struct drm_mode_set *modesets;
+
+       /**
+        * @buffers: Display buffers (optional).
+        */
+       struct drm_client_buffer **buffers;
+
+       /**
+        * @num_buffers: Number of backing buffers.
+        */
+       unsigned int num_buffers;
+};
+
+int drm_client_probe_displays(struct drm_client_dev *client, unsigned int 
num_buffers, u32 format);
+void drm_client_release_displays(struct drm_client_dev *client);
+int drm_client_display_commit_buffer(struct drm_client_display *display, 
unsigned int buffer);
+
+#define drm_client_for_each_display(display, client) \
+       list_for_each_entry(display, &(client)->displaylist, list)
+
 int drm_client_debugfs_init(struct drm_minor *minor);
 
 #endif
-- 
2.20.1

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

Reply via email to