Writeback connectors represent writeback engines which can write the CRTC output to a memory framebuffer. Add a writeback connector type and related support functions. Drivers should initialize a writeback connector with drm_writeback_connector_init() which takes care of setting up all the writeback-specific details on top of the normal functionality of drm_connector_init(). Writeback connectors have a WRITEBACK_FB_ID property, used to set the output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the supported writeback formats to userspace. When a framebuffer is attached to a writeback connector with the WRITEBACK_FB_ID property, it is used only once (for the commit in which it was included), and userspace can never read back the value of WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is attached to a CRTC. Changes since v1: - Added drm_writeback.c + documentation - Added helper to initialize writeback connector in one go - Added core checks - Squashed into a single commit - Dropped the client cap - Writeback framebuffers are no longer persistent Changes since v2: Daniel Vetter: - Subclass drm_connector to drm_writeback_connector - Relax check to allow CRTC to be set without an FB - Add some writeback_ prefixes - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary Gustavo Padovan: - Add drm_writeback_job to handle writeback signalling centrally Changes since v3: - Rebased - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS Chances since v4: - Embed a drm_encoder inside the drm_writeback_connector to reduce the amount of boilerplate code required from the drivers that are using it. Changes since v5: - Added Rob Clark's atomic_commit() vfunc to connector helper funcs, so that writeback jobs are committed from atomic helpers - Updated create_writeback_properties() signature to return an error code rather than a boolean false for failure. - Free writeback job with the connector state rather than when doing the cleanup_work() Changes since v7: - fix extraneous use of out_fence that is only introduced in a subsequent patch. Changes since v8: - whitespace changes pull from subsequent patch Changes since v9: - Revert the v6 changes that free the writeback job in the connector state cleanup and return to doing it in the cleanup_work() function
Unified · Split
| @@ -373,6 +373,15 @@ Connector Functions Reference · 372 unmodified lines | ||
| 373 | 373 | .. kernel-doc:: drivers/gpu/drm/drm_connector.c |
| 374 | 374 | :export: |
| 375 | 375 | |
| 376 | +Writeback Connectors | |
| 377 | +-------------------- | |
| 378 | + | |
| 379 | +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c | |
| 380 | + :doc: overview | |
| 381 | + | |
| 382 | +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c | |
| 383 | + :export: | |
| 384 | + | |
| 376 | 385 | Encoder Abstraction |
| 377 | 386 | =================== |
| 378 | 387 | |
| @@ -18,7 +18,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ · 17 unmodified lines | ||
| 18 | 18 | drm_encoder.o drm_mode_object.o drm_property.o \ |
| 19 | 19 | drm_plane.o drm_color_mgmt.o drm_print.o \ |
| 20 | 20 | drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \ |
| 21 | − drm_syncobj.o drm_lease.o | |
| 21 | + drm_syncobj.o drm_lease.o drm_writeback.o | |
| 22 | 22 | |
| 23 | 23 | drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o |
| 24 | 24 | drm-$(CONFIG_DRM_VM) += drm_vm.o |
| @@ -30,6 +30,7 @@ · 29 unmodified lines | ||
| 30 | 30 | #include <drm/drm_atomic.h> |
| 31 | 31 | #include <drm/drm_mode.h> |
| 32 | 32 | #include <drm/drm_print.h> |
| 33 | +#include <drm/drm_writeback.h> | |
| 33 | 34 | #include <linux/sync_file.h> |
| 34 | 35 | |
| 35 | 36 | #include "drm_crtc_internal.h" |
| @@ -690,6 +691,45 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p, · 654 unmodified lines | ||
| 690 | 691 | crtc->funcs->atomic_print_state(p, state); |
| 691 | 692 | } |
| 692 | 693 | |
| 694 | +/** | |
| 695 | + * drm_atomic_connector_check - check connector state | |
| 696 | + * @connector: connector to check | |
| 697 | + * @state: connector state to check | |
| 698 | + * | |
| 699 | + * Provides core sanity checks for connector state. | |
| 700 | + * | |
| 701 | + * RETURNS: | |
| 702 | + * Zero on success, error code on failure | |
| 703 | + */ | |
| 704 | +static int drm_atomic_connector_check(struct drm_connector *connector, | |
| 705 | + struct drm_connector_state *state) | |
| 706 | +{ | |
| 707 | + struct drm_crtc_state *crtc_state; | |
| 708 | + struct drm_writeback_job *writeback_job = state->writeback_job; | |
| 709 | + | |
| 710 | + if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) || !writeback_job) | |
| 711 | + return 0; | |
| 712 | + | |
| 713 | + if (writeback_job->fb && !state->crtc) { | |
| 714 | + DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n", | |
| 715 | + connector->base.id, connector->name); | |
| 716 | + return -EINVAL; | |
| 717 | + } | |
| 718 | + | |
| 719 | + if (state->crtc) | |
| 720 | + crtc_state = drm_atomic_get_existing_crtc_state(state->state, | |
| 721 | + state->crtc); | |
| 722 | + | |
| 723 | + if (writeback_job->fb && !crtc_state->active) { | |
| 724 | + DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n", | |
| 725 | + connector->base.id, connector->name, | |
| 726 | + state->crtc->base.id); | |
| 727 | + return -EINVAL; | |
| 728 | + } | |
| 729 | + | |
| 730 | + return 0; | |
| 731 | +} | |
| 732 | + | |
| 693 | 733 | /** |
| 694 | 734 | * drm_atomic_get_plane_state - get plane state |
| 695 | 735 | * @state: global atomic state object |
| @@ -1321,6 +1361,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, · 625 unmodified lines | ||
| 1321 | 1361 | return -EINVAL; |
| 1322 | 1362 | } |
| 1323 | 1363 | state->content_protection = val; |
| 1364 | + } else if (property == config->writeback_fb_id_property) { | |
| 1365 | + struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val); | |
| 1366 | + int ret = drm_atomic_set_writeback_fb_for_connector(state, fb); | |
| 1367 | + if (fb) | |
| 1368 | + drm_framebuffer_put(fb); | |
| 1369 | + return ret; | |
| 1324 | 1370 | } else if (connector->funcs->atomic_set_property) { |
| 1325 | 1371 | return connector->funcs->atomic_set_property(connector, |
| 1326 | 1372 | state, property, val); |
| @@ -1407,6 +1453,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector, · 80 unmodified lines | ||
| 1407 | 1453 | *val = state->scaling_mode; |
| 1408 | 1454 | } else if (property == connector->content_protection_property) { |
| 1409 | 1455 | *val = state->content_protection; |
| 1456 | + } else if (property == config->writeback_fb_id_property) { | |
| 1457 | + /* Writeback framebuffer is one-shot, write and forget */ | |
| 1458 | + *val = 0; | |
| 1410 | 1459 | } else if (connector->funcs->atomic_get_property) { |
| 1411 | 1460 | return connector->funcs->atomic_get_property(connector, |
| 1412 | 1461 | state, property, val); |
| @@ -1631,6 +1680,70 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state, · 218 unmodified lines | ||
| 1631 | 1680 | } |
| 1632 | 1681 | EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector); |
| 1633 | 1682 | |
| 1683 | +/* | |
| 1684 | + * drm_atomic_get_writeback_job - return or allocate a writeback job | |
| 1685 | + * @conn_state: Connector state to get the job for | |
| 1686 | + * | |
| 1687 | + * Writeback jobs have a different lifetime to the atomic state they are | |
| 1688 | + * associated with. This convenience function takes care of allocating a job | |
| 1689 | + * if there isn't yet one associated with the connector state, otherwise | |
| 1690 | + * it just returns the existing job. | |
| 1691 | + * | |
| 1692 | + * Returns: The writeback job for the given connector state | |
| 1693 | + */ | |
| 1694 | +static struct drm_writeback_job * | |
| 1695 | +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state) | |
| 1696 | +{ | |
| 1697 | + WARN_ON(conn_state->connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK); | |
| 1698 | + | |
| 1699 | + if (!conn_state->writeback_job) | |
| 1700 | + conn_state->writeback_job = | |
| 1701 | + kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL); | |
| 1702 | + | |
| 1703 | + return conn_state->writeback_job; | |
| 1704 | +} | |
| 1705 | + | |
| 1706 | +/** | |
| 1707 | + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer | |
| 1708 | + * @conn_state: atomic state object for the connector | |
| 1709 | + * @fb: fb to use for the connector | |
| 1710 | + * | |
| 1711 | + * This is used to set the framebuffer for a writeback connector, which outputs | |
| 1712 | + * to a buffer instead of an actual physical connector. | |
| 1713 | + * Changing the assigned framebuffer requires us to grab a reference to the new | |
| 1714 | + * fb and drop the reference to the old fb, if there is one. This function | |
| 1715 | + * takes care of all these details besides updating the pointer in the | |
| 1716 | + * state object itself. | |
| 1717 | + * | |
| 1718 | + * Note: The only way conn_state can already have an fb set is if the commit | |
| 1719 | + * sets the property more than once. | |
| 1720 | + * | |
| 1721 | + * See also: drm_writeback_connector_init() | |
| 1722 | + * | |
| 1723 | + * Returns: 0 on success | |
| 1724 | + */ | |
| 1725 | +int drm_atomic_set_writeback_fb_for_connector( | |
| 1726 | + struct drm_connector_state *conn_state, | |
| 1727 | + struct drm_framebuffer *fb) | |
| 1728 | +{ | |
| 1729 | + struct drm_writeback_job *job = | |
| 1730 | + drm_atomic_get_writeback_job(conn_state); | |
| 1731 | + if (!job) | |
| 1732 | + return -ENOMEM; | |
| 1733 | + | |
| 1734 | + drm_framebuffer_assign(&job->fb, fb); | |
| 1735 | + | |
| 1736 | + if (fb) | |
| 1737 | + DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n", | |
| 1738 | + fb->base.id, conn_state); | |
| 1739 | + else | |
| 1740 | + DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n", | |
| 1741 | + conn_state); | |
| 1742 | + | |
| 1743 | + return 0; | |
| 1744 | +} | |
| 1745 | +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector); | |
| 1746 | + | |
| 1634 | 1747 | /** |
| 1635 | 1748 | * drm_atomic_add_affected_connectors - add connectors for crtc |
| 1636 | 1749 | * @state: atomic state |
| @@ -1752,6 +1865,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state) · 115 unmodified lines | ||
| 1752 | 1865 | struct drm_plane_state *plane_state; |
| 1753 | 1866 | struct drm_crtc *crtc; |
| 1754 | 1867 | struct drm_crtc_state *crtc_state; |
| 1868 | + struct drm_connector *conn; | |
| 1869 | + struct drm_connector_state *conn_state; | |
| 1755 | 1870 | int i, ret = 0; |
| 1756 | 1871 | |
| 1757 | 1872 | DRM_DEBUG_ATOMIC("checking %p\n", state); |
| @@ -1774,6 +1889,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state) · 16 unmodified lines | ||
| 1774 | 1889 | } |
| 1775 | 1890 | } |
| 1776 | 1891 | |
| 1892 | + for_each_new_connector_in_state(state, conn, conn_state, i) { | |
| 1893 | + ret = drm_atomic_connector_check(conn, conn_state); | |
| 1894 | + if (ret) { | |
| 1895 | + DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n", | |
| 1896 | + conn->base.id, conn->name); | |
| 1897 | + return ret; | |
| 1898 | + } | |
| 1899 | + } | |
| 1900 | + | |
| 1777 | 1901 | if (config->funcs->atomic_check) |
| 1778 | 1902 | ret = config->funcs->atomic_check(state->dev, state); |
| 1779 | 1903 | |
| @@ -30,6 +30,7 @@ · 29 unmodified lines | ||
| 30 | 30 | #include <drm/drm_plane_helper.h> |
| 31 | 31 | #include <drm/drm_crtc_helper.h> |
| 32 | 32 | #include <drm/drm_atomic_helper.h> |
| 33 | +#include <drm/drm_writeback.h> | |
| 33 | 34 | #include <linux/dma-fence.h> |
| 34 | 35 | |
| 35 | 36 | #include "drm_crtc_helper_internal.h" |
| @@ -1172,6 +1173,25 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev, · 1136 unmodified lines | ||
| 1172 | 1173 | } |
| 1173 | 1174 | EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables); |
| 1174 | 1175 | |
| 1176 | +static void drm_atomic_helper_commit_writebacks(struct drm_device *dev, | |
| 1177 | + struct drm_atomic_state *old_state) | |
| 1178 | +{ | |
| 1179 | + struct drm_connector *connector; | |
| 1180 | + struct drm_connector_state *new_conn_state; | |
| 1181 | + int i; | |
| 1182 | + | |
| 1183 | + for_each_new_connector_in_state(old_state, connector, new_conn_state, i) { | |
| 1184 | + const struct drm_connector_helper_funcs *funcs; | |
| 1185 | + | |
| 1186 | + funcs = connector->helper_private; | |
| 1187 | + | |
| 1188 | + if (new_conn_state->writeback_job && new_conn_state->writeback_job->fb) { | |
| 1189 | + WARN_ON(connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK); | |
| 1190 | + funcs->atomic_commit(connector, new_conn_state->writeback_job); | |
| 1191 | + } | |
| 1192 | + } | |
| 1193 | +} | |
| 1194 | + | |
| 1175 | 1195 | /** |
| 1176 | 1196 | * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs |
| 1177 | 1197 | * @dev: DRM device |
| @@ -1251,6 +1271,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, · 73 unmodified lines | ||
| 1251 | 1271 | |
| 1252 | 1272 | drm_bridge_enable(encoder->bridge); |
| 1253 | 1273 | } |
| 1274 | + | |
| 1275 | + drm_atomic_helper_commit_writebacks(dev, old_state); | |
| 1254 | 1276 | } |
| 1255 | 1277 | EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables); |
| 1256 | 1278 | |
| @@ -3647,6 +3669,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector, · 2390 unmodified lines | ||
| 3647 | 3669 | if (state->crtc) |
| 3648 | 3670 | drm_connector_get(connector); |
| 3649 | 3671 | state->commit = NULL; |
| 3672 | + | |
| 3673 | + /* Don't copy over a writeback job, they are used only once */ | |
| 3674 | + state->writeback_job = NULL; | |
| 3650 | 3675 | } |
| 3651 | 3676 | EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state); |
| 3652 | 3677 | |
| @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = { · 86 unmodified lines | ||
| 87 | 87 | { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" }, |
| 88 | 88 | { DRM_MODE_CONNECTOR_DSI, "DSI" }, |
| 89 | 89 | { DRM_MODE_CONNECTOR_DPI, "DPI" }, |
| 90 | + { DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" }, | |
| 90 | 91 | }; |
| 91 | 92 | |
| 92 | 93 | void drm_connector_ida_init(void) |
| @@ -253,7 +254,8 @@ int drm_connector_init(struct drm_device *dev, · 160 unmodified lines | ||
| 253 | 254 | config->num_connector++; |
| 254 | 255 | spin_unlock_irq(&config->connector_list_lock); |
| 255 | 256 | |
| 256 | − if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL) | |
| 257 | + if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL && | |
| 258 | + connector_type != DRM_MODE_CONNECTOR_WRITEBACK) | |
| 257 | 259 | drm_object_attach_property(&connector->base, |
| 258 | 260 | config->edid_property, |
| 259 | 261 | 0); |
| @@ -0,0 +1,245 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0 | |
| 2 | +/* | |
| 3 | + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. | |
| 4 | + * Author: Brian Starkey <brian.starkey@arm.com> | |
| 5 | + * | |
| 6 | + * This program is free software and is provided to you under the terms of the | |
| 7 | + * GNU General Public License version 2 as published by the Free Software | |
| 8 | + * Foundation, and any use by you of this program is subject to the terms | |
| 9 | + * of such GNU licence. | |
| 10 | + */ | |
| 11 | + | |
| 12 | +#include <drm/drm_crtc.h> | |
| 13 | +#include <drm/drm_modeset_helper_vtables.h> | |
| 14 | +#include <drm/drm_property.h> | |
| 15 | +#include <drm/drm_writeback.h> | |
| 16 | +#include <drm/drmP.h> | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * DOC: overview | |
| 20 | + * | |
| 21 | + * Writeback connectors are used to expose hardware which can write the output | |
| 22 | + * from a CRTC to a memory buffer. They are used and act similarly to other | |
| 23 | + * types of connectors, with some important differences: | |
| 24 | + * - Writeback connectors don't provide a way to output visually to the user. | |
| 25 | + * - Writeback connectors should always report as "disconnected" (so that | |
| 26 | + * clients which don't understand them will ignore them). | |
| 27 | + * - Writeback connectors don't have EDID. | |
| 28 | + * | |
| 29 | + * A framebuffer may only be attached to a writeback connector when the | |
| 30 | + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the | |
| 31 | + * framebuffer applies only to a single commit (see below). A framebuffer may | |
| 32 | + * not be attached while the CRTC is off. | |
| 33 | + * | |
| 34 | + * Writeback connectors have some additional properties, which userspace | |
| 35 | + * can use to query and control them: | |
| 36 | + * | |
| 37 | + * "WRITEBACK_FB_ID": | |
| 38 | + * Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the | |
| 39 | + * framebuffer to be written by the writeback connector. This property is | |
| 40 | + * similar to the FB_ID property on planes, but will always read as zero | |
| 41 | + * and is not preserved across commits. | |
| 42 | + * Userspace must set this property to an output buffer every time it | |
| 43 | + * wishes the buffer to get filled. | |
| 44 | + * | |
| 45 | + * "WRITEBACK_PIXEL_FORMATS": | |
| 46 | + * Immutable blob property to store the supported pixel formats table. The | |
| 47 | + * data is an array of u32 DRM_FORMAT_* fourcc values. | |
| 48 | + * Userspace can use this blob to find out what pixel formats are supported | |
| 49 | + * by the connector's writeback engine. | |
| 50 | + */ | |
| 51 | + | |
| 52 | +static int create_writeback_properties(struct drm_device *dev) | |
| 53 | +{ | |
| 54 | + struct drm_property *prop; | |
| 55 | + | |
| 56 | + if (!dev->mode_config.writeback_fb_id_property) { | |
| 57 | + prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC, | |
| 58 | + "WRITEBACK_FB_ID", | |
| 59 | + DRM_MODE_OBJECT_FB); | |
| 60 | + if (!prop) | |
| 61 | + return -ENOMEM; | |
| 62 | + dev->mode_config.writeback_fb_id_property = prop; | |
| 63 | + } | |
| 64 | + | |
| 65 | + if (!dev->mode_config.writeback_pixel_formats_property) { | |
| 66 | + prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | | |
| 67 | + DRM_MODE_PROP_ATOMIC | | |
| 68 | + DRM_MODE_PROP_IMMUTABLE, | |
| 69 | + "WRITEBACK_PIXEL_FORMATS", 0); | |
| 70 | + if (!prop) | |
| 71 | + return -ENOMEM; | |
| 72 | + dev->mode_config.writeback_pixel_formats_property = prop; | |
| 73 | + } | |
| 74 | + | |
| 75 | + return 0; | |
| 76 | +} | |
| 77 | + | |
| 78 | +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = { | |
| 79 | + .destroy = drm_encoder_cleanup, | |
| 80 | +}; | |
| 81 | + | |
| 82 | +/** | |
| 83 | + * drm_writeback_connector_init - Initialize a writeback connector and its properties | |
| 84 | + * @dev: DRM device | |
| 85 | + * @wb_connector: Writeback connector to initialize | |
| 86 | + * @con_funcs: Connector funcs vtable | |
| 87 | + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder | |
| 88 | + * @formats: Array of supported pixel formats for the writeback engine | |
| 89 | + * @n_formats: Length of the formats array | |
| 90 | + * | |
| 91 | + * This function creates the writeback-connector-specific properties if they | |
| 92 | + * have not been already created, initializes the connector as | |
| 93 | + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property | |
| 94 | + * values. It will also create an internal encoder associated with the | |
| 95 | + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for | |
| 96 | + * the encoder helper. | |
| 97 | + * | |
| 98 | + * Drivers should always use this function instead of drm_connector_init() to | |
| 99 | + * set up writeback connectors. | |
| 100 | + * | |
| 101 | + * Returns: 0 on success, or a negative error code | |
| 102 | + */ | |
| 103 | +int drm_writeback_connector_init(struct drm_device *dev, | |
| 104 | + struct drm_writeback_connector *wb_connector, | |
| 105 | + const struct drm_connector_funcs *con_funcs, | |
| 106 | + const struct drm_encoder_helper_funcs *enc_helper_funcs, | |
| 107 | + const u32 *formats, int n_formats) | |
| 108 | +{ | |
| 109 | + struct drm_property_blob *blob; | |
| 110 | + struct drm_connector *connector = &wb_connector->base; | |
| 111 | + struct drm_mode_config *config = &dev->mode_config; | |
| 112 | + int ret = create_writeback_properties(dev); | |
| 113 | + | |
| 114 | + if (ret != 0) | |
| 115 | + return ret; | |
| 116 | + | |
| 117 | + blob = drm_property_create_blob(dev, n_formats * sizeof(*formats), | |
| 118 | + formats); | |
| 119 | + if (IS_ERR(blob)) | |
| 120 | + return PTR_ERR(blob); | |
| 121 | + | |
| 122 | + drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs); | |
| 123 | + ret = drm_encoder_init(dev, &wb_connector->encoder, | |
| 124 | + &drm_writeback_encoder_funcs, | |
| 125 | + DRM_MODE_ENCODER_VIRTUAL, NULL); | |
| 126 | + if (ret) | |
| 127 | + goto fail; | |
| 128 | + | |
| 129 | + connector->interlace_allowed = 0; | |
| 130 | + | |
| 131 | + ret = drm_connector_init(dev, connector, con_funcs, | |
| 132 | + DRM_MODE_CONNECTOR_WRITEBACK); | |
| 133 | + if (ret) | |
| 134 | + goto connector_fail; | |
| 135 | + | |
| 136 | + ret = drm_mode_connector_attach_encoder(connector, | |
| 137 | + &wb_connector->encoder); | |
| 138 | + if (ret) | |
| 139 | + goto attach_fail; | |
| 140 | + | |
| 141 | + INIT_LIST_HEAD(&wb_connector->job_queue); | |
| 142 | + spin_lock_init(&wb_connector->job_lock); | |
| 143 | + | |
| 144 | + drm_object_attach_property(&connector->base, | |
| 145 | + config->writeback_fb_id_property, 0); | |
| 146 | + | |
| 147 | + drm_object_attach_property(&connector->base, | |
| 148 | + config->writeback_pixel_formats_property, | |
| 149 | + blob->base.id); | |
| 150 | + wb_connector->pixel_formats_blob_ptr = blob; | |
| 151 | + | |
| 152 | + return 0; | |
| 153 | + | |
| 154 | +attach_fail: | |
| 155 | + drm_connector_cleanup(connector); | |
| 156 | +connector_fail: | |
| 157 | + drm_encoder_cleanup(&wb_connector->encoder); | |
| 158 | +fail: | |
| 159 | + drm_property_blob_put(blob); | |
| 160 | + return ret; | |
| 161 | +} | |
| 162 | +EXPORT_SYMBOL(drm_writeback_connector_init); | |
| 163 | + | |
| 164 | +/** | |
| 165 | + * drm_writeback_queue_job - Queue a writeback job for later signalling | |
| 166 | + * @wb_connector: The writeback connector to queue a job on | |
| 167 | + * @job: The job to queue | |
| 168 | + * | |
| 169 | + * This function adds a job to the job_queue for a writeback connector. It | |
| 170 | + * should be considered to take ownership of the writeback job, and so any other | |
| 171 | + * references to the job must be cleared after calling this function. | |
| 172 | + * | |
| 173 | + * Drivers must ensure that for a given writeback connector, jobs are queued in | |
| 174 | + * exactly the same order as they will be completed by the hardware (and | |
| 175 | + * signaled via drm_writeback_signal_completion). | |
| 176 | + * | |
| 177 | + * For every call to drm_writeback_queue_job() there must be exactly one call to | |
| 178 | + * drm_writeback_signal_completion() | |
| 179 | + * | |
| 180 | + * See also: drm_writeback_signal_completion() | |
| 181 | + */ | |
| 182 | +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector, | |
| 183 | + struct drm_writeback_job *job) | |
| 184 | +{ | |
| 185 | + unsigned long flags; | |
| 186 | + | |
| 187 | + spin_lock_irqsave(&wb_connector->job_lock, flags); | |
| 188 | + list_add_tail(&job->list_entry, &wb_connector->job_queue); | |
| 189 | + spin_unlock_irqrestore(&wb_connector->job_lock, flags); | |
| 190 | +} | |
| 191 | +EXPORT_SYMBOL(drm_writeback_queue_job); | |
| 192 | + | |
| 193 | +/* | |
| 194 | + * @cleanup_work: deferred cleanup of a writeback job | |
| 195 | + * | |
| 196 | + * The job cannot be cleaned up directly in drm_writeback_signal_completion, | |
| 197 | + * because it may be called in interrupt context. Dropping the framebuffer | |
| 198 | + * reference can sleep, and so the cleanup is deferred to a workqueue. | |
| 199 | + */ | |
| 200 | +static void cleanup_work(struct work_struct *work) | |
| 201 | +{ | |
| 202 | + struct drm_writeback_job *job = container_of(work, | |
| 203 | + struct drm_writeback_job, | |
| 204 | + cleanup_work); | |
| 205 | + drm_framebuffer_put(job->fb); | |
| 206 | + kfree(job); | |
| 207 | +} | |
| 208 | + | |
| 209 | + | |
| 210 | +/** | |
| 211 | + * drm_writeback_signal_completion - Signal the completion of a writeback job | |
| 212 | + * @wb_connector: The writeback connector whose job is complete | |
| 213 | + * | |
| 214 | + * Drivers should call this to signal the completion of a previously queued | |
| 215 | + * writeback job. It should be called as soon as possible after the hardware | |
| 216 | + * has finished writing, and may be called from interrupt context. | |
| 217 | + * It is the driver's responsibility to ensure that for a given connector, the | |
| 218 | + * hardware completes writeback jobs in the same order as they are queued. | |
| 219 | + * | |
| 220 | + * Unless the driver is holding its own reference to the framebuffer, it must | |
| 221 | + * not be accessed after calling this function. | |
| 222 | + * | |
| 223 | + * See also: drm_writeback_queue_job() | |
| 224 | + */ | |
| 225 | +void | |
| 226 | +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector) | |
| 227 | +{ | |
| 228 | + unsigned long flags; | |
| 229 | + struct drm_writeback_job *job; | |
| 230 | + | |
| 231 | + spin_lock_irqsave(&wb_connector->job_lock, flags); | |
| 232 | + job = list_first_entry_or_null(&wb_connector->job_queue, | |
| 233 | + struct drm_writeback_job, | |
| 234 | + list_entry); | |
| 235 | + if (job) | |
| 236 | + list_del(&job->list_entry); | |
| 237 | + spin_unlock_irqrestore(&wb_connector->job_lock, flags); | |
| 238 | + | |
| 239 | + if (WARN_ON(!job)) | |
| 240 | + return; | |
| 241 | + | |
| 242 | + INIT_WORK(&job->cleanup_work, cleanup_work); | |
| 243 | + queue_work(system_long_wq, &job->cleanup_work); | |
| 244 | +} | |
| 245 | +EXPORT_SYMBOL(drm_writeback_signal_completion); | |
| @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state, · 593 unmodified lines | ||
| 594 | 594 | int __must_check |
| 595 | 595 | drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state, |
| 596 | 596 | struct drm_crtc *crtc); |
| 597 | +int drm_atomic_set_writeback_fb_for_connector( | |
| 598 | + struct drm_connector_state *conn_state, | |
| 599 | + struct drm_framebuffer *fb); | |
| 597 | 600 | int __must_check |
| 598 | 601 | drm_atomic_add_affected_connectors(struct drm_atomic_state *state, |
| 599 | 602 | struct drm_crtc *crtc); |
| @@ -437,6 +437,19 @@ struct drm_connector_state { · 436 unmodified lines | ||
| 437 | 437 | * protection. This is most commonly used for HDCP. |
| 438 | 438 | */ |
| 439 | 439 | unsigned int content_protection; |
| 440 | + | |
| 441 | + /** | |
| 442 | + * @writeback_job: Writeback job for writeback connectors | |
| 443 | + * | |
| 444 | + * Holds the framebuffer for a writeback connector. As the writeback | |
| 445 | + * completion may be asynchronous to the normal commit cycle, the | |
| 446 | + * writeback job lifetime is managed separately from the normal atomic | |
| 447 | + * state by this object. | |
| 448 | + * | |
| 449 | + * See also: drm_writeback_queue_job() and | |
| 450 | + * drm_writeback_signal_completion() | |
| 451 | + */ | |
| 452 | + struct drm_writeback_job *writeback_job; | |
| 440 | 453 | }; |
| 441 | 454 | |
| 442 | 455 | /** |
| @@ -784,6 +784,21 @@ struct drm_mode_config { · 783 unmodified lines | ||
| 784 | 784 | */ |
| 785 | 785 | struct drm_property *panel_orientation_property; |
| 786 | 786 | |
| 787 | + /** | |
| 788 | + * @writeback_fb_id_property: Property for writeback connectors, storing | |
| 789 | + * the ID of the output framebuffer. | |
| 790 | + * See also: drm_writeback_connector_init() | |
| 791 | + */ | |
| 792 | + struct drm_property *writeback_fb_id_property; | |
| 793 | + | |
| 794 | + /** | |
| 795 | + * @writeback_pixel_formats_property: Property for writeback connectors, | |
| 796 | + * storing an array of the supported pixel formats for the writeback | |
| 797 | + * engine (read-only). | |
| 798 | + * See also: drm_writeback_connector_init() | |
| 799 | + */ | |
| 800 | + struct drm_property *writeback_pixel_formats_property; | |
| 801 | + | |
| 787 | 802 | /* dumb ioctl parameters */ |
| 788 | 803 | uint32_t preferred_depth, prefer_shadow; |
| 789 | 804 | |
| @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs { · 973 unmodified lines | ||
| 974 | 974 | */ |
| 975 | 975 | int (*atomic_check)(struct drm_connector *connector, |
| 976 | 976 | struct drm_connector_state *state); |
| 977 | + | |
| 978 | + /** | |
| 979 | + * @atomic_commit: | |
| 980 | + * | |
| 981 | + * This hook is to be used by drivers implementing writeback connectors | |
| 982 | + * that need a point when to commit the writeback job to the hardware. | |
| 983 | + * | |
| 984 | + * This callback is used by the atomic modeset helpers. | |
| 985 | + */ | |
| 986 | + void (*atomic_commit)(struct drm_connector *connector, | |
| 987 | + struct drm_writeback_job *writeback_job); | |
| 977 | 988 | }; |
| 978 | 989 | |
| 979 | 990 | /** |
| @@ -0,0 +1,91 @@ | ||
| 1 | +/* SPDX-License-Identifier: GPL-2.0 */ | |
| 2 | +/* | |
| 3 | + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. | |
| 4 | + * Author: Brian Starkey <brian.starkey@arm.com> | |
| 5 | + * | |
| 6 | + * This program is free software and is provided to you under the terms of the | |
| 7 | + * GNU General Public License version 2 as published by the Free Software | |
| 8 | + * Foundation, and any use by you of this program is subject to the terms | |
| 9 | + * of such GNU licence. | |
| 10 | + */ | |
| 11 | + | |
| 12 | +#ifndef __DRM_WRITEBACK_H__ | |
| 13 | +#define __DRM_WRITEBACK_H__ | |
| 14 | +#include <drm/drm_connector.h> | |
| 15 | +#include <drm/drm_encoder.h> | |
| 16 | +#include <linux/workqueue.h> | |
| 17 | + | |
| 18 | +struct drm_writeback_connector { | |
| 19 | + struct drm_connector base; | |
| 20 | + | |
| 21 | + /** | |
| 22 | + * @encoder: Internal encoder used by the connector to fulfill | |
| 23 | + * the DRM framework requirements. The users of the | |
| 24 | + * @drm_writeback_connector control the behaviour of the @encoder | |
| 25 | + * by passing the @enc_funcs parameter to drm_writeback_connector_init() | |
| 26 | + * function. | |
| 27 | + */ | |
| 28 | + struct drm_encoder encoder; | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * @pixel_formats_blob_ptr: | |
| 32 | + * | |
| 33 | + * DRM blob property data for the pixel formats list on writeback | |
| 34 | + * connectors | |
| 35 | + * See also drm_writeback_connector_init() | |
| 36 | + */ | |
| 37 | + struct drm_property_blob *pixel_formats_blob_ptr; | |
| 38 | + | |
| 39 | + /** @job_lock: Protects job_queue */ | |
| 40 | + spinlock_t job_lock; | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * @job_queue: | |
| 44 | + * | |
| 45 | + * Holds a list of a connector's writeback jobs; the last item is the | |
| 46 | + * most recent. The first item may be either waiting for the hardware | |
| 47 | + * to begin writing, or currently being written. | |
| 48 | + * | |
| 49 | + * See also: drm_writeback_queue_job() and | |
| 50 | + * drm_writeback_signal_completion() | |
| 51 | + */ | |
| 52 | + struct list_head job_queue; | |
| 53 | +}; | |
| 54 | + | |
| 55 | +struct drm_writeback_job { | |
| 56 | + /** | |
| 57 | + * @cleanup_work: | |
| 58 | + * | |
| 59 | + * Used to allow drm_writeback_signal_completion to defer dropping the | |
| 60 | + * framebuffer reference to a workqueue | |
| 61 | + */ | |
| 62 | + struct work_struct cleanup_work; | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * @list_entry: | |
| 66 | + * | |
| 67 | + * List item for the writeback connector's @job_queue | |
| 68 | + */ | |
| 69 | + struct list_head list_entry; | |
| 70 | + | |
| 71 | + /** | |
| 72 | + * @fb: | |
| 73 | + * | |
| 74 | + * Framebuffer to be written to by the writeback connector. Do not set | |
| 75 | + * directly, use drm_atomic_set_writeback_fb_for_connector() | |
| 76 | + */ | |
| 77 | + struct drm_framebuffer *fb; | |
| 78 | +}; | |
| 79 | + | |
| 80 | +int drm_writeback_connector_init(struct drm_device *dev, | |
| 81 | + struct drm_writeback_connector *wb_connector, | |
| 82 | + const struct drm_connector_funcs *con_funcs, | |
| 83 | + const struct drm_encoder_helper_funcs *enc_helper_funcs, | |
| 84 | + const u32 *formats, int n_formats); | |
| 85 | + | |
| 86 | +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector, | |
| 87 | + struct drm_writeback_job *job); | |
| 88 | + | |
| 89 | +void drm_writeback_cleanup_job(struct drm_writeback_job *job); | |
| 90 | +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector); | |
| 91 | +#endif | |
| @@ -351,6 +351,7 @@ enum drm_mode_subconnector { · 350 unmodified lines | ||
| 351 | 351 | #define DRM_MODE_CONNECTOR_VIRTUAL 15 |
| 352 | 352 | #define DRM_MODE_CONNECTOR_DSI 16 |
| 353 | 353 | #define DRM_MODE_CONNECTOR_DPI 17 |
| 354 | +#define DRM_MODE_CONNECTOR_WRITEBACK 18 | |
| 354 | 355 | |
| 355 | 356 | struct drm_mode_get_connector { |
| 356 | 357 | |