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@xxxxxxxxxxx> --- 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 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx