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 | .. kernel-doc:: drivers/gpu/drm/drm_connector.c | 373 | .. kernel-doc:: drivers/gpu/drm/drm_connector.c |
| 374 | :export: | 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 | Encoder Abstraction | 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 | drm_encoder.o drm_mode_object.o drm_property.o \ | 18 | drm_encoder.o drm_mode_object.o drm_property.o \ |
| 19 | drm_plane.o drm_color_mgmt.o drm_print.o \ | 19 | drm_plane.o drm_color_mgmt.o drm_print.o \ |
| 20 | drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \ | 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 | drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o | 23 | drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o |
| 24 | drm-$(CONFIG_DRM_VM) += drm_vm.o | 24 | drm-$(CONFIG_DRM_VM) += drm_vm.o |
| @@ -30,6 +30,7 @@ · 29 unmodified lines | |||
| 30 | #include <drm/drm_atomic.h> | 30 | #include <drm/drm_atomic.h> |
| 31 | #include <drm/drm_mode.h> | 31 | #include <drm/drm_mode.h> |
| 32 | #include <drm/drm_print.h> | 32 | #include <drm/drm_print.h> |
| 33 | #include <drm/drm_writeback.h> | ||
| 33 | #include <linux/sync_file.h> | 34 | #include <linux/sync_file.h> |
| 34 | 35 | ||
| 35 | #include "drm_crtc_internal.h" | 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 | crtc->funcs->atomic_print_state(p, state); | 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 | * drm_atomic_get_plane_state - get plane state | 734 | * drm_atomic_get_plane_state - get plane state |
| 695 | * @state: global atomic state object | 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 | return -EINVAL; | 1361 | return -EINVAL; |
| 1322 | } | 1362 | } |
| 1323 | state->content_protection = val; | 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 | } else if (connector->funcs->atomic_set_property) { | 1370 | } else if (connector->funcs->atomic_set_property) { |
| 1325 | return connector->funcs->atomic_set_property(connector, | 1371 | return connector->funcs->atomic_set_property(connector, |
| 1326 | state, property, val); | 1372 | state, property, val); |
| @@ -1407,6 +1453,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector, · 80 unmodified lines | |||
| 1407 | *val = state->scaling_mode; | 1453 | *val = state->scaling_mode; |
| 1408 | } else if (property == connector->content_protection_property) { | 1454 | } else if (property == connector->content_protection_property) { |
| 1409 | *val = state->content_protection; | 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 | } else if (connector->funcs->atomic_get_property) { | 1459 | } else if (connector->funcs->atomic_get_property) { |
| 1411 | return connector->funcs->atomic_get_property(connector, | 1460 | return connector->funcs->atomic_get_property(connector, |
| 1412 | state, property, val); | 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 | EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector); | 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 | * drm_atomic_add_affected_connectors - add connectors for crtc | 1748 | * drm_atomic_add_affected_connectors - add connectors for crtc |
| 1636 | * @state: atomic state | 1749 | * @state: atomic state |
| @@ -1752,6 +1865,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state) · 115 unmodified lines | |||
| 1752 | struct drm_plane_state *plane_state; | 1865 | struct drm_plane_state *plane_state; |
| 1753 | struct drm_crtc *crtc; | 1866 | struct drm_crtc *crtc; |
| 1754 | struct drm_crtc_state *crtc_state; | 1867 | struct drm_crtc_state *crtc_state; |
| 1868 | struct drm_connector *conn; | ||
| 1869 | struct drm_connector_state *conn_state; | ||
| 1755 | int i, ret = 0; | 1870 | int i, ret = 0; |
| 1756 | 1871 | ||
| 1757 | DRM_DEBUG_ATOMIC("checking %p\n", state); | 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 | if (config->funcs->atomic_check) | 1901 | if (config->funcs->atomic_check) |
| 1778 | ret = config->funcs->atomic_check(state->dev, state); | 1902 | ret = config->funcs->atomic_check(state->dev, state); |
| 1779 | 1903 | ||
| @@ -30,6 +30,7 @@ · 29 unmodified lines | |||
| 30 | #include <drm/drm_plane_helper.h> | 30 | #include <drm/drm_plane_helper.h> |
| 31 | #include <drm/drm_crtc_helper.h> | 31 | #include <drm/drm_crtc_helper.h> |
| 32 | #include <drm/drm_atomic_helper.h> | 32 | #include <drm/drm_atomic_helper.h> |
| 33 | #include <drm/drm_writeback.h> | ||
| 33 | #include <linux/dma-fence.h> | 34 | #include <linux/dma-fence.h> |
| 34 | 35 | ||
| 35 | #include "drm_crtc_helper_internal.h" | 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 | EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables); | 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 | * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs | 1196 | * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs |
| 1177 | * @dev: DRM device | 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 | drm_bridge_enable(encoder->bridge); | 1272 | drm_bridge_enable(encoder->bridge); |
| 1253 | } | 1273 | } |
| 1274 | |||
| 1275 | drm_atomic_helper_commit_writebacks(dev, old_state); | ||
| 1254 | } | 1276 | } |
| 1255 | EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables); | 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 | if (state->crtc) | 3669 | if (state->crtc) |
| 3648 | drm_connector_get(connector); | 3670 | drm_connector_get(connector); |
| 3649 | state->commit = NULL; | 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 | EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state); | 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 | { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" }, | 87 | { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" }, |
| 88 | { DRM_MODE_CONNECTOR_DSI, "DSI" }, | 88 | { DRM_MODE_CONNECTOR_DSI, "DSI" }, |
| 89 | { DRM_MODE_CONNECTOR_DPI, "DPI" }, | 89 | { DRM_MODE_CONNECTOR_DPI, "DPI" }, |
| 90 | { DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" }, | ||
| 90 | }; | 91 | }; |
| 91 | 92 | ||
| 92 | void drm_connector_ida_init(void) | 93 | void drm_connector_ida_init(void) |
| @@ -253,7 +254,8 @@ int drm_connector_init(struct drm_device *dev, · 160 unmodified lines | |||
| 253 | config->num_connector++; | 254 | config->num_connector++; |
| 254 | spin_unlock_irq(&config->connector_list_lock); | 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 | drm_object_attach_property(&connector->base, | 259 | drm_object_attach_property(&connector->base, |
| 258 | config->edid_property, | 260 | config->edid_property, |
| 259 | 0); | 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 | int __must_check | 594 | int __must_check |
| 595 | drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state, | 595 | drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state, |
| 596 | struct drm_crtc *crtc); | 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 | int __must_check | 600 | int __must_check |
| 598 | drm_atomic_add_affected_connectors(struct drm_atomic_state *state, | 601 | drm_atomic_add_affected_connectors(struct drm_atomic_state *state, |
| 599 | struct drm_crtc *crtc); | 602 | struct drm_crtc *crtc); |
| @@ -437,6 +437,19 @@ struct drm_connector_state { · 436 unmodified lines | |||
| 437 | * protection. This is most commonly used for HDCP. | 437 | * protection. This is most commonly used for HDCP. |
| 438 | */ | 438 | */ |
| 439 | unsigned int content_protection; | 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 | struct drm_property *panel_orientation_property; | 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 | /* dumb ioctl parameters */ | 802 | /* dumb ioctl parameters */ |
| 788 | uint32_t preferred_depth, prefer_shadow; | 803 | uint32_t preferred_depth, prefer_shadow; |
| 789 | 804 | ||
| @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs { · 973 unmodified lines | |||
| 974 | */ | 974 | */ |
| 975 | int (*atomic_check)(struct drm_connector *connector, | 975 | int (*atomic_check)(struct drm_connector *connector, |
| 976 | struct drm_connector_state *state); | 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 | #define DRM_MODE_CONNECTOR_VIRTUAL 15 | 351 | #define DRM_MODE_CONNECTOR_VIRTUAL 15 |
| 352 | #define DRM_MODE_CONNECTOR_DSI 16 | 352 | #define DRM_MODE_CONNECTOR_DSI 16 |
| 353 | #define DRM_MODE_CONNECTOR_DPI 17 | 353 | #define DRM_MODE_CONNECTOR_DPI 17 |
| 354 | #define DRM_MODE_CONNECTOR_WRITEBACK 18 | ||
| 354 | 355 | ||
| 355 | struct drm_mode_get_connector { | 356 | struct drm_mode_get_connector { |
| 356 | 357 | ||