From 79b5dc168fc44c899efccefb2a374dd38f45e264 Mon Sep 17 00:00:00 2001 From: Oliver Bestmann Date: Sat, 27 Dec 2025 19:10:40 +0100 Subject: [PATCH] drm: apple: get hdr to output on internal panel (hacky) This commit enables hacky support for hdr output in gnome/kde. While it works pretty well with the internal display of my macbook m1 pro. I've tried this on gnome 49 and got to view some hdr content in firefox and play hdr video using mpv. [2] We need a few things for gnome/kde to realize that the output supports hdr [1]. * Gnome is looking for the colorspace property on the connector and wants it to indicate support for BT2020_RGB. * It wants to see a HDR_OUTPUT_METADATA property on the connector. * Gnome reads the edid blob and checks for support of different hdr transfer functions. It requires the PQ function to be available to expose bt2020 as an available colorspace [3]. This hacky commit will just return an edid record of a different hdr capable screen. This is a hack. When HDR is enabled, the compositor will set the color property and the HDR_OUTPUT_METADATA property to tell the kernel about how the surface data is to be interpreted. I use this in the apple_dcp driver to switch the transfer function from sdr to hdr. Backlight is another topic: Since gnome 49, gnome will switch to a virtual/soft backlight control when hdr is activated. With an absolute transfer function like PQ that actually makes kind of sense. The actual backlight is then just set to 100%. This patch forces the backlight into max brightness when the HDR_OUTPUT_METADATA property is present (currently even more than normal sdr max brightness). When enabling hdr, you need to dial the virtual backlight all the way down, otherwise it'll crush highlights in hdr content. I think macOS also has an internal virtual backlight control. It is continously adjusting the bightness value of the (real) backlight when displaying hdr content while the percived brightness of sdr content on screen stays the same by varying the virtual backlight (i guess). Knobs: For experimenting I've exposed a few parameters from apple_dcp: * swap_hdr_colorspace: Controls the dcp colorspace value when hdr is enabled. Defaults to DCP_COLORSPACE_BG_BT2020 (9). * swap_hdr_transferfunc: Controls the dcp transfer function when hdr is enabled. Defaults to DCP_XFER_FUNC_HDR (16). * swap_hdr_brightness: The backlights brightness value when hdr is enabled. I choose to use the maxiumum value observed in dcp traces when hdr content was displayed on screen: 0x98ffffc0. This is way above the maximum allowed sdr value of about 0x7fe07fc0. * rgb2101010_dcp_format: The fourcc format to use for DRM_FORMAT_XRGB2101010 surfaces for hdr content. SDR uses w30r while hdr uses l10r by default. [1] https://gitlab.gnome.org/GNOME/mutter/-/blob/31e0de8800308c87655100e1d6ffc42f58625a34/src/backends/native/meta-output-kms.c#L533 [2] https://wiki.archlinux.org/title/HDR#HDR_video_samples [3] https://gitlab.gnome.org/GNOME/mutter/-/blob/31e0de8800308c87655100e1d6ffc42f58625a34/src/backends/meta-monitor.c#L217 Signed-off-by: Oliver Bestmann --- drivers/gpu/drm/apple/apple_drv.c | 13 ++++++ drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 13 ++++++ drivers/gpu/drm/apple/iomfb.c | 54 ++++++++++++++++++++++- drivers/gpu/drm/apple/iomfb_internal.h | 3 +- drivers/gpu/drm/apple/iomfb_template.c | 60 ++++++++++++++++++++++++-- 6 files changed, 138 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 345bcf4eb769fe..c7232f4916d055 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -507,6 +507,19 @@ static int apple_probe_per_dcp(struct device *dev, crtc->dcp = dcp; dcp_link(dcp, crtc, connector); + // gnome wants to see BT2020_RGB + u32 supported_colorspaces = + BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65) | + BIT(DRM_MODE_COLORIMETRY_BT2020_RGB); + + // allow userspace to signal the color space used + if (!drm_mode_create_dp_colorspace_property(&connector->base, supported_colorspaces)) + drm_connector_attach_colorspace_property(&connector->base); + + // allow userspace to expose hdr output metadata. + // this indicates "hey i am rendering hdr content now" + drm_connector_attach_hdr_output_metadata_property(&connector->base); + return drm_connector_attach_encoder(&connector->base, &enc->base); } diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 2c31d2a8cef09d..757a4fda223b14 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -91,6 +91,7 @@ struct dcp_brightness { struct backlight_device *bl_dev; u32 maximum; u32 dac; + u32 dac_hdr_restore; int nits; int scale; bool update; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 72b6d0fd7460d7..99b99148370ea5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -55,6 +55,19 @@ static bool unstable_edid = true; module_param(unstable_edid, bool, 0644); MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); +u32 swap_hdr_colorspace = DCP_COLORSPACE_BG_BT2020; +module_param(swap_hdr_colorspace, uint, 0644); +MODULE_PARM_DESC(swap_hdr_colorspace, "Colorspace to use for primary swap with hdr"); + +u32 swap_hdr_transferfunc = DCP_XFER_FUNC_HDR; +module_param(swap_hdr_transferfunc, uint, 0644); +MODULE_PARM_DESC(swap_hdr_transferfunc, "Transfer function to use for primary swap with hdr"); + +u32 swap_hdr_brightness = 0x98FFFFC0; // max brightness observed with hdr content +module_param(swap_hdr_brightness, uint, 0644); +MODULE_PARM_DESC(swap_hdr_brightness, "Force the display into this brightness when in hdr mode"); + + /* copied and simplified from drm_vblank.c */ static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5b0f97253e3fc0..ebd01edca3be06 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -357,7 +357,22 @@ struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) .h = drm_rect_height(rect) }; } -u32 drm_format_to_dcp(u32 drm) +u32 rgb2101010_dcp_format = fourcc_code('r', '0', '1', 'l'); +module_param(rgb2101010_dcp_format, uint, 0644); +MODULE_PARM_DESC(rgb2101010_dcp_format, "dcp color format for DRM_FORMAT_XRGB2101010 in hdr"); + +u32 drm_format_to_dcp_hdr(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB2101010: + return rgb2101010_dcp_format; + } + + pr_warn("DRM format %X not supported in DCP with HDR\n", drm); + return 0; +} + +u32 drm_format_to_dcp_sdr(u32 drm) { switch (drm) { case DRM_FORMAT_XRGB8888: @@ -372,7 +387,7 @@ u32 drm_format_to_dcp(u32 drm) return fourcc_code('r', '0', '3', 'w'); } - pr_warn("DRM format %X not supported in DCP\n", drm); + pr_warn("DRM format %X not supported in DCP with SDR\n", drm); return 0; } @@ -408,6 +423,41 @@ int dcp_get_modes(struct drm_connector *connector) apple_connector->drm_edid = edid; } } + + if (!apple_connector->drm_edid && dcp->nr_modes && dcp_has_panel(dcp)) { + // HACK: fake some edid entry for the iinternal panel. This value here is actually for a + // totally different screen, but it has the hdr property set. that property is required + // for gnome and kde to display the "enable hdr" toggle. + static const u8 edidbuf[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x34, 0xa9, 0x1c, 0xd1, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x19, 0x01, 0x03, 0x80, 0xdd, 0x7d, 0x78, + 0x0a, 0x06, 0x12, 0xaf, 0x51, 0x4e, 0xad, 0x24, 0x0b, 0x4c, 0x51, 0x20, + 0x08, 0x00, 0xa9, 0xc0, 0xa9, 0x40, 0x90, 0x40, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x08, 0xe8, 0x00, 0x30, 0xf2, 0x70, + 0x5a, 0x80, 0xb0, 0x58, 0x8a, 0x00, 0x1c, 0x00, 0x74, 0x00, 0x00, 0x1e, + 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38, 0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, + 0x1c, 0x00, 0x74, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x45, + 0x54, 0x2d, 0x4d, 0x44, 0x4e, 0x48, 0x4d, 0x31, 0x30, 0x0a, 0x20, 0x20, + 0x00, 0x00, 0x00, 0xfd, 0x00, 0x17, 0x79, 0x0f, 0x96, 0x3c, 0x00, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x75, 0x02, 0x03, 0x41, 0xb1, + 0x57, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x66, 0x65, 0x64, 0x63, 0x62, 0x3f, + 0x10, 0x1f, 0x05, 0x14, 0x22, 0x21, 0x20, 0x04, 0x13, 0x02, 0x11, 0x01, + 0xe3, 0x05, 0xe0, 0x00, 0x6e, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x38, 0x3c, + 0x20, 0x08, 0x80, 0x01, 0x02, 0x03, 0x04, 0x67, 0xd8, 0x5d, 0xc4, 0x01, + 0x78, 0x80, 0x03, 0xe2, 0x00, 0xff, 0xe2, 0x0f, 0x63, 0xe3, 0x06, 0x0d, + 0x01, 0x28, 0x3c, 0x80, 0xa0, 0x70, 0xb0, 0x23, 0x40, 0x30, 0x20, 0x36, + 0x00, 0x66, 0x00, 0x64, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x5a + }; + + pr_info("injecting fake edid buf\n"); + const struct drm_edid *edid = drm_edid_alloc(edidbuf, sizeof(edidbuf)); + drm_connector_update_edid_property(&dcp->connector->base, drm_edid_raw(edid)); + } + if (dcp->nr_modes && apple_connector->drm_edid) drm_edid_connector_update(connector, apple_connector->drm_edid); diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h index 09f8857d30c341..0866436fe99163 100644 --- a/drivers/gpu/drm/apple/iomfb_internal.h +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -116,7 +116,8 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context); */ struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect); -u32 drm_format_to_dcp(u32 drm); +u32 drm_format_to_dcp_sdr(u32 drm); +u32 drm_format_to_dcp_hdr(u32 drm); /* The user may own drm_display_mode, so we need to search for our copy */ struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0a1b9495128562..fec552df49bdb1 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1264,6 +1264,24 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, return 0; } +extern u32 swap_hdr_colorspace; +extern u32 swap_hdr_transferfunc; +extern u32 swap_hdr_brightness; + +static bool drm_format_maybe_hdr(u32 format) +{ + switch (format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return false; + + default: + return true; + } +} + void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state) { struct drm_plane *plane; @@ -1374,11 +1392,47 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru if (obj) req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; + u32 format = drm_format_to_dcp_sdr(fb->format->format); + u32 colorspace = DCP_COLORSPACE_NATIVE; + u32 transferfunc = DCP_XFER_FUNC_SDR; + + if (plane->type == DRM_PLANE_TYPE_PRIMARY && drm_format_maybe_hdr(fb->format->format)) { + if (dcp->connector->base.state->hdr_output_metadata) { + // HACK: hard code the values kwin & mutter use: + // DCP_COLORSPACE_BT2020 + // DCP_XFER_FUNC_HDR # looks like PQ + // + // TODO need to pick the current colorspace and + // the transfer function from the drm properties + // (colorspace + HDR_OUTPUT_METADATA). + // + // For testing: You can set swap_hdr_colorspace + // and swap_hdr_transferfunc as parameters from + // userspace to try out different values. + // + colorspace = swap_hdr_colorspace; + transferfunc = swap_hdr_transferfunc; + format = drm_format_to_dcp_hdr(fb->format->format); + + // force brightness to full for now. + if (!dcp->brightness.dac_hdr_restore && swap_hdr_brightness) { + dcp->brightness.dac_hdr_restore = dcp->brightness.dac; + dcp->brightness.dac = swap_hdr_brightness; + dcp->brightness.update = true; + } + } else if (dcp->brightness.dac_hdr_restore) { + // restore pre hdr brightness + dcp->brightness.dac = dcp->brightness.dac_hdr_restore; + dcp->brightness.dac_hdr_restore = 0; + dcp->brightness.update = true; + } + } + req->surf[l] = (struct DCP_FW_NAME(dcp_surface)){ .is_premultiplied = is_premultiplied, - .format = drm_format_to_dcp(fb->format->format), - .xfer_func = DCP_XFER_FUNC_SDR, - .colorspace = DCP_COLORSPACE_NATIVE, + .format = format, + .xfer_func = transferfunc, + .colorspace = colorspace, .stride = fb->pitches[0], .width = fb->width, .height = fb->height,