@@ -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)
{
@@ -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
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 <noralf@tronnes.org> --- drivers/gpu/drm/drm_client.c | 415 +++++++++++++++++++++++++++++++++++ include/drm/drm_client.h | 80 +++++++ 2 files changed, 495 insertions(+)