// SPDX-License-Identifier: MIT
#include <linux/export.h>
#include <drm/drm_atomic.h>
#include <drm/drm_connector.h>
#include <drm/drm_edid.h>
#include <drm/drm_modes.h>
#include <drm/drm_print.h>
#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_cec_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
/**
* DOC : hdmi helpers
*
* These functions contain an implementation of the HDMI specification
* in the form of KMS helpers .
*
* It contains TMDS character rate computation , automatic selection of
* output formats , infoframes generation , etc .
*
* Infoframes Compliance
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Drivers using the helpers will expose the various infoframes
* generated according to the HDMI specification in debugfs .
*
* Compliance can then be tested using ` ` edid - decode ` ` from the ` ` v4l - utils ` ` project
* ( https : //git.linuxtv.org/v4l-utils.git/). A sample run would look like:
*
* . . code - block : : bash
*
* # edid - decode \
* - I / sys / kernel / debug / dri / 1 / HDMI - A - 1 / infoframes / audio \
* - I / sys / kernel / debug / dri / 1 / HDMI - A - 1 / infoframes / avi \
* - I / sys / kernel / debug / dri / 1 / HDMI - A - 1 / infoframes / hdmi \
* - I / sys / kernel / debug / dri / 1 / HDMI - A - 1 / infoframes / hdr_drm \
* - I / sys / kernel / debug / dri / 1 / HDMI - A - 1 / infoframes / spd \
* / sys / class / drm / card1 - HDMI - A - 1 / edid \
* - c
*
* edid - decode ( hex ) :
*
* 00 ff ff ff ff ff ff 00 1 e 6 d f4 5 b 1 e ef 06 00
* 07 20 01 03 80 2 f 34 78 ea 24 05 af 4 f 42 ab 25
* 0 f 50 54 21 08 00 d1 c0 61 40 45 40 01 01 01 01
* 01 01 01 01 01 01 98 d0 00 40 a1 40 d4 b0 30 20
* 3 a 00 d1 0 b 12 00 00 1 a 00 00 00 fd 00 3 b 3 d 1 e
* b2 31 00 0 a 20 20 20 20 20 20 00 00 00 fc 00 4 c
* 47 20 53 44 51 48 44 0 a 20 20 20 20 00 00 00 ff
* 00 32 30 37 4 e 54 52 4 c 44 43 34 33 30 0 a 01 46
*
* 02 03 42 72 23 09 07 07 4 d 01 03 04 90 12 13 1 f
* 22 5 d 5 e 5 f 60 61 83 01 00 00 6 d 03 0 c 00 10 00
* b8 3 c 20 00 60 01 02 03 67 d8 5 d c4 01 78 80 03
* e3 0 f 00 18 e2 00 6 a e3 05 c0 00 e6 06 05 01 52
* 52 51 11 5 d 00 a0 a0 40 29 b0 30 20 3 a 00 d1 0 b
* 12 00 00 1 a 00 00 00 00 00 00 00 00 00 00 00 00
* 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c3
*
* - - - - - - - - - - - - - - - -
*
* Block 0 , Base EDID :
* EDID Structure Version & Revision : 1 . 3
* Vendor & Product Identification :
* Manufacturer : GSM
* Model : 23540
* Serial Number : 454430 ( 0 x0006ef1e )
* Made in : week 7 of 2022
* Basic Display Parameters & Features :
* Digital display
* Maximum image size : 47 cm x 52 cm
* Gamma : 2 . 20
* DPMS levels : Standby Suspend Off
* RGB color display
* First detailed timing is the preferred timing
* Color Characteristics :
* Red : 0 . 6835 , 0 . 3105
* Green : 0 . 2587 , 0 . 6679
* Blue : 0 . 1445 , 0 . 0585
* White : 0 . 3134 , 0 . 3291
* Established Timings I & II :
* DMT 0 x04 : 640 x480 59 . 940476 Hz 4 : 3 31 . 469 kHz 25 . 175000 MHz
* DMT 0 x09 : 800 x600 60 . 316541 Hz 4 : 3 37 . 879 kHz 40 . 000000 MHz
* DMT 0 x10 : 1024 x768 60 . 003840 Hz 4 : 3 48 . 363 kHz 65 . 000000 MHz
* Standard Timings :
* DMT 0 x52 : 1920 x1080 60 . 000000 Hz 16 : 9 67 . 500 kHz 148 . 500000 MHz
* DMT 0 x10 : 1024 x768 60 . 003840 Hz 4 : 3 48 . 363 kHz 65 . 000000 MHz
* DMT 0 x09 : 800 x600 60 . 316541 Hz 4 : 3 37 . 879 kHz 40 . 000000 MHz
* Detailed Timing Descriptors :
* DTD 1 : 2560 x2880 59 . 966580 Hz 8 : 9 185 . 417 kHz 534 . 000000 MHz ( 465 mm x 523 mm )
* Hfront 48 Hsync 32 Hback 240 Hpol P
* Vfront 3 Vsync 10 Vback 199 Vpol N
* Display Range Limits :
* Monitor ranges ( GTF ) : 59 - 61 Hz V , 30 - 178 kHz H , max dotclock 490 MHz
* Display Product Name : ' LG SDQHD '
* Display Product Serial Number : ' 207 NTRLDC430 '
* Extension blocks : 1
* Checksum : 0 x46
*
* - - - - - - - - - - - - - - - -
*
* Block 1 , CTA - 861 Extension Block :
* Revision : 3
* Basic audio support
* Supports YCbCr 4 : 4 : 4
* Supports YCbCr 4 : 2 : 2
* Native detailed modes : 2
* Audio Data Block :
* Linear PCM :
* Max channels : 2
* Supported sample rates ( kHz ) : 48 44 . 1 32
* Supported sample sizes ( bits ) : 24 20 16
* Video Data Block :
* VIC 1 : 640 x480 59 . 940476 Hz 4 : 3 31 . 469 kHz 25 . 175000 MHz
* VIC 3 : 720 x480 59 . 940060 Hz 16 : 9 31 . 469 kHz 27 . 000000 MHz
* VIC 4 : 1280 x720 60 . 000000 Hz 16 : 9 45 . 000 kHz 74 . 250000 MHz
* VIC 16 : 1920 x1080 60 . 000000 Hz 16 : 9 67 . 500 kHz 148 . 500000 MHz ( native )
* VIC 18 : 720 x576 50 . 000000 Hz 16 : 9 31 . 250 kHz 27 . 000000 MHz
* VIC 19 : 1280 x720 50 . 000000 Hz 16 : 9 37 . 500 kHz 74 . 250000 MHz
* VIC 31 : 1920 x1080 50 . 000000 Hz 16 : 9 56 . 250 kHz 148 . 500000 MHz
* VIC 34 : 1920 x1080 30 . 000000 Hz 16 : 9 33 . 750 kHz 74 . 250000 MHz
* VIC 93 : 3840 x2160 24 . 000000 Hz 16 : 9 54 . 000 kHz 297 . 000000 MHz
* VIC 94 : 3840 x2160 25 . 000000 Hz 16 : 9 56 . 250 kHz 297 . 000000 MHz
* VIC 95 : 3840 x2160 30 . 000000 Hz 16 : 9 67 . 500 kHz 297 . 000000 MHz
* VIC 96 : 3840 x2160 50 . 000000 Hz 16 : 9 112 . 500 kHz 594 . 000000 MHz
* VIC 97 : 3840 x2160 60 . 000000 Hz 16 : 9 135 . 000 kHz 594 . 000000 MHz
* Speaker Allocation Data Block :
* FL / FR - Front Left / Right
* Vendor - Specific Data Block ( HDMI ) , OUI 00 - 0 C - 03 :
* Source physical address : 1 . 0 . 0 . 0
* Supports_AI
* DC_36bit
* DC_30bit
* DC_Y444
* Maximum TMDS clock : 300 MHz
* Extended HDMI video details :
* HDMI VICs :
* HDMI VIC 1 : 3840 x2160 30 . 000000 Hz 16 : 9 67 . 500 kHz 297 . 000000 MHz
* HDMI VIC 2 : 3840 x2160 25 . 000000 Hz 16 : 9 56 . 250 kHz 297 . 000000 MHz
* HDMI VIC 3 : 3840 x2160 24 . 000000 Hz 16 : 9 54 . 000 kHz 297 . 000000 MHz
* Vendor - Specific Data Block ( HDMI Forum ) , OUI C4 - 5 D - D8 :
* Version : 1
* Maximum TMDS Character Rate : 600 MHz
* SCDC Present
* Supports 12 - bits / component Deep Color 4 : 2 : 0 Pixel Encoding
* Supports 10 - bits / component Deep Color 4 : 2 : 0 Pixel Encoding
* YCbCr 4 : 2 : 0 Capability Map Data Block :
* VIC 96 : 3840 x2160 50 . 000000 Hz 16 : 9 112 . 500 kHz 594 . 000000 MHz
* VIC 97 : 3840 x2160 60 . 000000 Hz 16 : 9 135 . 000 kHz 594 . 000000 MHz
* Video Capability Data Block :
* YCbCr quantization : No Data
* RGB quantization : Selectable ( via AVI Q )
* PT scan behavior : Always Underscanned
* IT scan behavior : Always Underscanned
* CE scan behavior : Always Underscanned
* Colorimetry Data Block :
* BT2020YCC
* BT2020RGB
* HDR Static Metadata Data Block :
* Electro optical transfer functions :
* Traditional gamma - SDR luminance range
* SMPTE ST2084
* Supported static metadata descriptors :
* Static metadata type 1
* Desired content max luminance : 82 ( 295 . 365 cd / m ^ 2 )
* Desired content max frame - average luminance : 82 ( 295 . 365 cd / m ^ 2 )
* Desired content min luminance : 81 ( 0 . 298 cd / m ^ 2 )
* Detailed Timing Descriptors :
* DTD 2 : 2560 x2880 29 . 986961 Hz 8 : 9 87 . 592 kHz 238 . 250000 MHz ( 465 mm x 523 mm )
* Hfront 48 Hsync 32 Hback 80 Hpol P
* Vfront 3 Vsync 10 Vback 28 Vpol N
* Checksum : 0 xc3 Unused space in Extension Block : 43 bytes
*
* - - - - - - - - - - - - - - - -
*
* edid - decode 1 . 29 . 0 - 5346
* edid - decode SHA : c363e9aa6d70 2025 - 03 - 11 11 : 41 : 18
*
* Warnings :
*
* Block 1 , CTA - 861 Extension Block :
* IT Video Formats are overscanned by default , but normally this should be underscanned .
* Video Data Block : VIC 1 and the first DTD are not identical . Is this intended ?
* Video Data Block : All VICs are in ascending order , and the first ( preferred ) VIC < = 4 , is that intended ?
* Video Capability Data Block : Set Selectable YCbCr Quantization to avoid interop issues .
* Video Capability Data Block : S_PT is equal to S_IT and S_CE , so should be set to 0 instead .
* Colorimetry Data Block : Set the sRGB colorimetry bit to avoid interop issues .
* Display Product Serial Number is set , so the Serial Number in the Base EDID should be 0 .
* EDID :
* Base EDID : Some timings are out of range of the Monitor Ranges :
* Vertical Freq : 24 . 000 - 60 . 317 Hz ( Monitor : 59 . 000 - 61 . 000 Hz )
* Horizontal Freq : 31 . 250 - 185 . 416 kHz ( Monitor : 30 . 000 - 178 . 000 kHz )
* Maximum Clock : 594 . 000 MHz ( Monitor : 490 . 000 MHz )
*
* Failures :
*
* Block 1 , CTA - 861 Extension Block :
* Video Capability Data Block : IT video formats are always underscanned , but bit 7 of Byte 3 of the CTA - 861 Extension header is set to overscanned .
* EDID :
* CTA - 861 : Native progressive timings are a mix of several resolutions .
*
* EDID conformity : FAIL
*
* = = = = = = = = = = = = = = = =
*
* InfoFrame of ' / sys / kernel / debug / dri / 1 / HDMI - A - 1 / infoframes / audio ' was empty .
*
* = = = = = = = = = = = = = = = =
*
* edid - decode InfoFrame ( hex ) :
*
* 82 02 0 d 31 12 28 04 00 00 00 00 00 00 00 00 00
* 00
*
* - - - - - - - - - - - - - - - -
*
* HDMI InfoFrame Checksum : 0 x31
*
* AVI InfoFrame
* Version : 2
* Length : 13
* Y : Color Component Sample Format : RGB
* A : Active Format Information Present : Yes
* B : Bar Data Present : Bar Data not present
* S : Scan Information : Composed for an underscanned display
* C : Colorimetry : No Data
* M : Picture Aspect Ratio : 16 : 9
* R : Active Portion Aspect Ratio : 8
* ITC : IT Content : No Data
* EC : Extended Colorimetry : xvYCC601
* Q : RGB Quantization Range : Limited Range
* SC : Non - Uniform Picture Scaling : No Known non - uniform scaling
* YQ : YCC Quantization Range : Limited Range
* CN : IT Content Type : Graphics
* PR : Pixel Data Repetition Count : 0
* Line Number of End of Top Bar : 0
* Line Number of Start of Bottom Bar : 0
* Pixel Number of End of Left Bar : 0
* Pixel Number of Start of Right Bar : 0
*
* - - - - - - - - - - - - - - - -
*
* AVI InfoFrame conformity : PASS
*
* = = = = = = = = = = = = = = = =
*
* edid - decode InfoFrame ( hex ) :
*
* 81 01 05 49 03 0 c 00 20 01
*
* - - - - - - - - - - - - - - - -
*
* HDMI InfoFrame Checksum : 0 x49
*
* Vendor - Specific InfoFrame ( HDMI ) , OUI 00 - 0 C - 03
* Version : 1
* Length : 5
* HDMI Video Format : HDMI_VIC is present
* HDMI VIC 1 : 3840 x2160 30 . 000000 Hz 16 : 9 67 . 500 kHz 297 . 000000 MHz
*
* - - - - - - - - - - - - - - - -
*
* Vendor - Specific InfoFrame ( HDMI ) , OUI 00 - 0 C - 03 conformity : PASS
*
* = = = = = = = = = = = = = = = =
*
* InfoFrame of ' / sys / kernel / debug / dri / 1 / HDMI - A - 1 / infoframes / hdr_drm ' was empty .
*
* = = = = = = = = = = = = = = = =
*
* edid - decode InfoFrame ( hex ) :
*
* 83 01 19 93 42 72 6 f 61 64 63 6 f 6 d 56 69 64 65
* 6 f 63 6 f 72 65 00 00 00 00 00 00 00 09
*
* - - - - - - - - - - - - - - - -
*
* HDMI InfoFrame Checksum : 0 x93
*
* Source Product Description InfoFrame
* Version : 1
* Length : 25
* Vendor Name : ' Broadcom '
* Product Description : ' Videocore '
* Source Information : PC general
*
* - - - - - - - - - - - - - - - -
*
* Source Product Description InfoFrame conformity : PASS
*
* Testing
* ~ ~ ~ ~ ~ ~ ~
*
* The helpers have unit testing and can be tested using kunit with :
*
* . . code - block : : bash
*
* $ . / tools / testing / kunit / kunit . py run \
* - - kunitconfig = drivers / gpu / drm / tests \
* drm_atomic_helper_connector_hdmi_ *
*/
/**
* _ _ drm_atomic_helper_connector_hdmi_reset ( ) - Initializes all HDMI @ drm_connector_state resources
* @ connector : DRM connector
* @ new_conn_state : connector state to reset
*
* Initializes all HDMI resources from a @ drm_connector_state without
* actually allocating it . This is useful for HDMI drivers , in
* combination with _ _ drm_atomic_helper_connector_reset ( ) or
* drm_atomic_helper_connector_reset ( ) .
*/
void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
struct drm_connector_state *new_conn_state)
{
unsigned int max_bpc = connector->max_bpc;
new_conn_state->max_bpc = max_bpc;
new_conn_state->max_requested_bpc = max_bpc;
new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO;
}
EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset);
static const struct drm_display_mode *
connector_state_get_mode(const struct drm_connector_state *conn_state)
{
struct drm_atomic_state *state;
struct drm_crtc_state *crtc_state;
struct drm_crtc *crtc;
state = conn_state->state;
if (!state)
return NULL;
crtc = conn_state->crtc;
if (!crtc)
return NULL;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (!crtc_state)
return NULL;
return &crtc_state->mode;
}
static bool hdmi_is_limited_range(const struct drm_connector *connector,
const struct drm_connector_state *conn_state)
{
const struct drm_display_info *info = &connector->display_info;
const struct drm_display_mode *mode =
connector_state_get_mode(conn_state);
/*
* The Broadcast RGB property only applies to RGB format , and
* i915 just assumes limited range for YCbCr output , so let ' s
* just do the same .
*/
if (conn_state->hdmi.output_format != HDMI_COLORSPACE_RGB)
return true ;
if (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_FULL)
return false ;
if (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED)
return true ;
if (!info->is_hdmi)
return false ;
return drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED;
}
static bool
sink_supports_format_bpc(const struct drm_connector *connector,
const struct drm_display_info *info,
const struct drm_display_mode *mode,
unsigned int format, unsigned int bpc)
{
struct drm_device *dev = connector->dev;
u8 vic = drm_match_cea_mode(mode);
/*
* CTA - 861 - F , section 5 . 4 - Color Coding & Quantization states
* that the bpc must be 8 , 10 , 12 or 16 except for the default
* 640 x480 VIC1 where the value must be 8 .
*
* The definition of default here is ambiguous but the spec
* refers to VIC1 being the default timing in several occasions
* so our understanding is that for the default timing ( ie ,
* VIC1 ) , the bpc must be 8 .
*/
if (vic == 1 && bpc != 8 ) {
drm_dbg_kms(dev, "VIC1 requires a bpc of 8, got %u\n" , bpc);
return false ;
}
if (!info->is_hdmi &&
(format != HDMI_COLORSPACE_RGB || bpc != 8 )) {
drm_dbg_kms(dev, "DVI Monitors require an RGB output at 8 bpc\n" );
return false ;
}
if (!(connector->hdmi.supported_formats & BIT(format))) {
drm_dbg_kms(dev, "%s format unsupported by the connector.\n" ,
drm_hdmi_connector_get_output_format_name(format));
return false ;
}
if (drm_mode_is_420_only(info, mode) && format != HDMI_COLORSPACE_YUV420) {
drm_dbg_kms(dev, "Mode can be only supported in YUV420 format.\n" );
return false ;
}
switch (format) {
case HDMI_COLORSPACE_RGB:
drm_dbg_kms(dev, "RGB Format, checking the constraints.\n" );
/*
* In some cases , like when the EDID readout fails , or
* is not an HDMI compliant EDID for some reason , the
* color_formats field will be blank and not report any
* format supported . In such a case , assume that RGB is
* supported so we can keep things going and light up
* the display .
*/
if (!(info->color_formats & DRM_COLOR_FORMAT_RGB444))
drm_warn(dev, "HDMI Sink doesn't support RGB, something's wrong.\n" );
if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
drm_dbg_kms(dev, "10 BPC but sink doesn't support Deep Color 30.\n" );
return false ;
}
if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
drm_dbg_kms(dev, "12 BPC but sink doesn't support Deep Color 36.\n" );
return false ;
}
drm_dbg_kms(dev, "RGB format supported in that configuration.\n" );
return true ;
case HDMI_COLORSPACE_YUV420:
drm_dbg_kms(dev, "YUV420 format, checking the constraints.\n" );
if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR420)) {
drm_dbg_kms(dev, "Sink doesn't support YUV420.\n" );
return false ;
}
if (!drm_mode_is_420(info, mode)) {
drm_dbg_kms(dev, "Mode cannot be supported in YUV420 format.\n" );
return false ;
}
if (bpc == 10 && !(info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)) {
drm_dbg_kms(dev, "10 BPC but sink doesn't support Deep Color 30.\n" );
return false ;
}
if (bpc == 12 && !(info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)) {
drm_dbg_kms(dev, "12 BPC but sink doesn't support Deep Color 36.\n" );
return false ;
}
if (bpc == 16 && !(info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48)) {
drm_dbg_kms(dev, "16 BPC but sink doesn't support Deep Color 48.\n" );
return false ;
}
drm_dbg_kms(dev, "YUV420 format supported in that configuration.\n" );
return true ;
case HDMI_COLORSPACE_YUV422:
drm_dbg_kms(dev, "YUV422 format, checking the constraints.\n" );
if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR422)) {
drm_dbg_kms(dev, "Sink doesn't support YUV422.\n" );
return false ;
}
if (bpc > 12 ) {
drm_dbg_kms(dev, "YUV422 only supports 12 bpc or lower.\n" );
return false ;
}
/*
* HDMI Spec 1 . 3 - Section 6 . 5 Pixel Encodings and Color Depth
* states that Deep Color is not relevant for YUV422 so we
* don ' t need to check the Deep Color bits in the EDIDs here .
*/
drm_dbg_kms(dev, "YUV422 format supported in that configuration.\n" );
return true ;
case HDMI_COLORSPACE_YUV444:
drm_dbg_kms(dev, "YUV444 format, checking the constraints.\n" );
if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR444)) {
drm_dbg_kms(dev, "Sink doesn't support YUV444.\n" );
return false ;
}
if (bpc == 10 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_30)) {
drm_dbg_kms(dev, "10 BPC but sink doesn't support Deep Color 30.\n" );
return false ;
}
if (bpc == 12 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_36)) {
drm_dbg_kms(dev, "12 BPC but sink doesn't support Deep Color 36.\n" );
return false ;
}
drm_dbg_kms(dev, "YUV444 format supported in that configuration.\n" );
return true ;
}
drm_dbg_kms(dev, "Unsupported pixel format.\n" );
return false ;
}
static enum drm_mode_status
hdmi_clock_valid(const struct drm_connector *connector,
const struct drm_display_mode *mode,
unsigned long long clock)
{
const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
const struct drm_display_info *info = &connector->display_info;
if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000 )
return MODE_CLOCK_HIGH;
if (funcs && funcs->tmds_char_rate_valid) {
enum drm_mode_status status;
status = funcs->tmds_char_rate_valid(connector, mode, clock);
if (status != MODE_OK)
return status;
}
return MODE_OK;
}
static int
hdmi_compute_clock(const struct drm_connector *connector,
struct drm_connector_state *conn_state,
const struct drm_display_mode *mode,
unsigned int bpc, enum hdmi_colorspace fmt)
{
enum drm_mode_status status;
unsigned long long clock;
clock = drm_hdmi_compute_mode_clock(mode, bpc, fmt);
if (!clock)
return -EINVAL;
status = hdmi_clock_valid(connector, mode, clock);
if (status != MODE_OK)
return -EINVAL;
conn_state->hdmi.tmds_char_rate = clock;
return 0 ;
}
static bool
hdmi_try_format_bpc(const struct drm_connector *connector,
struct drm_connector_state *conn_state,
const struct drm_display_mode *mode,
unsigned int bpc, enum hdmi_colorspace fmt)
{
const struct drm_display_info *info = &connector->display_info;
struct drm_device *dev = connector->dev;
int ret;
drm_dbg_kms(dev, "Trying %s output format with %u bpc\n" ,
drm_hdmi_connector_get_output_format_name(fmt),
bpc);
if (!sink_supports_format_bpc(connector, info, mode, fmt, bpc)) {
drm_dbg_kms(dev, "%s output format not supported with %u bpc\n" ,
drm_hdmi_connector_get_output_format_name(fmt),
bpc);
return false ;
}
ret = hdmi_compute_clock(connector, conn_state, mode, bpc, fmt);
if (ret) {
drm_dbg_kms(dev, "Couldn't compute clock for %s output format and %u bpc\n" ,
drm_hdmi_connector_get_output_format_name(fmt),
bpc);
return false ;
}
drm_dbg_kms(dev, "%s output format supported with %u bpc (TMDS char rate: %llu Hz)\n" ,
drm_hdmi_connector_get_output_format_name(fmt),
bpc, conn_state->hdmi.tmds_char_rate);
return true ;
}
static int
hdmi_compute_format_bpc(const struct drm_connector *connector,
struct drm_connector_state *conn_state,
const struct drm_display_mode *mode,
unsigned int max_bpc, enum hdmi_colorspace fmt)
{
struct drm_device *dev = connector->dev;
unsigned int bpc;
int ret;
for (bpc = max_bpc; bpc >= 8 ; bpc -= 2 ) {
ret = hdmi_try_format_bpc(connector, conn_state, mode, bpc, fmt);
if (!ret)
continue ;
conn_state->hdmi.output_bpc = bpc;
conn_state->hdmi.output_format = fmt;
drm_dbg_kms(dev,
"Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n" ,
mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
conn_state->hdmi.output_bpc,
drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format),
conn_state->hdmi.tmds_char_rate);
return 0 ;
}
drm_dbg_kms(dev, "Failed. %s output format not supported for any bpc count.\n" ,
drm_hdmi_connector_get_output_format_name(fmt));
return -EINVAL;
}
static int
hdmi_compute_config(const struct drm_connector *connector,
struct drm_connector_state *conn_state,
const struct drm_display_mode *mode)
{
unsigned int max_bpc = clamp_t(unsigned int ,
conn_state->max_bpc,
8 , connector->max_bpc);
int ret;
ret = hdmi_compute_format_bpc(connector, conn_state, mode, max_bpc,
HDMI_COLORSPACE_RGB);
if (ret) {
if (connector->ycbcr_420_allowed) {
ret = hdmi_compute_format_bpc(connector, conn_state,
mode, max_bpc,
HDMI_COLORSPACE_YUV420);
if (ret)
drm_dbg_kms(connector->dev,
"YUV420 output format doesn't work.\n" );
} else {
drm_dbg_kms(connector->dev,
"YUV420 output format not allowed for connector.\n" );
ret = -EINVAL;
}
}
return ret;
}
static int hdmi_generate_avi_infoframe(const struct drm_connector *connector,
struct drm_connector_state *conn_state)
{
const struct drm_display_mode *mode =
connector_state_get_mode(conn_state);
struct drm_connector_hdmi_infoframe *infoframe =
&conn_state->hdmi.infoframes.avi;
struct hdmi_avi_infoframe *frame =
&infoframe->data.avi;
bool is_limited_range = conn_state->hdmi.is_limited_range;
enum hdmi_quantization_range rgb_quant_range =
is_limited_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL;
int ret;
infoframe->set = false ;
ret = drm_hdmi_avi_infoframe_from_display_mode(frame, connector, mode);
if (ret)
return ret;
frame->colorspace = conn_state->hdmi.output_format;
/*
* FIXME : drm_hdmi_avi_infoframe_quant_range ( ) doesn ' t handle
* YUV formats at all at the moment , so if we ever support YUV
* formats this needs to be revised .
*/
drm_hdmi_avi_infoframe_quant_range(frame, connector, mode, rgb_quant_range);
drm_hdmi_avi_infoframe_colorimetry(frame, conn_state);
drm_hdmi_avi_infoframe_bars(frame, conn_state);
infoframe->set = true ;
return 0 ;
}
static int hdmi_generate_spd_infoframe(const struct drm_connector *connector,
struct drm_connector_state *conn_state)
{
struct drm_connector_hdmi_infoframe *infoframe =
&conn_state->hdmi.infoframes.spd;
struct hdmi_spd_infoframe *frame =
&infoframe->data.spd;
int ret;
infoframe->set = false ;
ret = hdmi_spd_infoframe_init(frame,
connector->hdmi.vendor,
connector->hdmi.product);
if (ret)
return ret;
frame->sdi = HDMI_SPD_SDI_PC;
infoframe->set = true ;
return 0 ;
}
static int hdmi_generate_hdr_infoframe(const struct drm_connector *connector,
struct drm_connector_state *conn_state)
{
struct drm_connector_hdmi_infoframe *infoframe =
&conn_state->hdmi.infoframes.hdr_drm;
struct hdmi_drm_infoframe *frame =
&infoframe->data.drm;
int ret;
infoframe->set = false ;
if (connector->max_bpc < 10 )
return 0 ;
if (!conn_state->hdr_output_metadata)
return 0 ;
ret = drm_hdmi_infoframe_set_hdr_metadata(frame, conn_state);
if (ret)
return ret;
infoframe->set = true ;
return 0 ;
}
static int hdmi_generate_hdmi_vendor_infoframe(const struct drm_connector *connector,
struct drm_connector_state *conn_state)
{
const struct drm_display_info *info = &connector->display_info;
const struct drm_display_mode *mode =
connector_state_get_mode(conn_state);
struct drm_connector_hdmi_infoframe *infoframe =
&conn_state->hdmi.infoframes.hdmi;
struct hdmi_vendor_infoframe *frame =
&infoframe->data.vendor.hdmi;
int ret;
infoframe->set = false ;
if (!info->has_hdmi_infoframe)
return 0 ;
ret = drm_hdmi_vendor_infoframe_from_display_mode(frame, connector, mode);
if (ret)
return ret;
infoframe->set = true ;
return 0 ;
}
static int
hdmi_generate_infoframes(const struct drm_connector *connector,
struct drm_connector_state *conn_state)
{
const struct drm_display_info *info = &connector->display_info;
int ret;
if (!info->is_hdmi)
return 0 ;
ret = hdmi_generate_avi_infoframe(connector, conn_state);
if (ret)
return ret;
ret = hdmi_generate_spd_infoframe(connector, conn_state);
if (ret)
return ret;
/*
* Audio Infoframes will be generated by ALSA , and updated by
* drm_atomic_helper_connector_hdmi_update_audio_infoframe ( ) .
*/
ret = hdmi_generate_hdr_infoframe(connector, conn_state);
if (ret)
return ret;
ret = hdmi_generate_hdmi_vendor_infoframe(connector, conn_state);
if (ret)
return ret;
return 0 ;
}
/**
* drm_atomic_helper_connector_hdmi_check ( ) - Helper to check HDMI connector atomic state
* @ connector : DRM Connector
* @ state : the DRM State object
*
* Provides a default connector state check handler for HDMI connectors .
* Checks that a desired connector update is valid , and updates various
* fields of derived state .
*
* RETURNS :
* Zero on success , or an errno code otherwise .
*/
int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
struct drm_atomic_state *state)
{
struct drm_connector_state *old_conn_state =
drm_atomic_get_old_connector_state(state, connector);
struct drm_connector_state *new_conn_state =
drm_atomic_get_new_connector_state(state, connector);
const struct drm_display_mode *mode =
connector_state_get_mode(new_conn_state);
int ret;
if (!new_conn_state->crtc || !new_conn_state->best_encoder)
return 0 ;
ret = hdmi_compute_config(connector, new_conn_state, mode);
if (ret)
return ret;
new_conn_state->hdmi.is_limited_range = hdmi_is_limited_range(connector, new_conn_state);
ret = hdmi_generate_infoframes(connector, new_conn_state);
if (ret)
return ret;
if (old_conn_state->hdmi.broadcast_rgb != new_conn_state->hdmi.broadcast_rgb ||
old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc ||
old_conn_state->hdmi.output_format != new_conn_state->hdmi.output_format) {
struct drm_crtc *crtc = new_conn_state->crtc;
struct drm_crtc_state *crtc_state;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
crtc_state->mode_changed = true ;
}
return 0 ;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_check);
/**
* drm_hdmi_connector_mode_valid ( ) - Check if mode is valid for HDMI connector
* @ connector : DRM connector to validate the mode
* @ mode : Display mode to validate
*
* Generic . mode_valid implementation for HDMI connectors .
*/
enum drm_mode_status
drm_hdmi_connector_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode)
{
unsigned long long clock;
clock = drm_hdmi_compute_mode_clock(mode, 8 , HDMI_COLORSPACE_RGB);
if (!clock)
return MODE_ERROR;
return hdmi_clock_valid(connector, mode, clock);
}
EXPORT_SYMBOL(drm_hdmi_connector_mode_valid);
static int clear_device_infoframe(struct drm_connector *connector,
enum hdmi_infoframe_type type)
{
const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
struct drm_device *dev = connector->dev;
int ret;
drm_dbg_kms(dev, "Clearing infoframe type 0x%x\n" , type);
if (!funcs || !funcs->clear_infoframe) {
drm_dbg_kms(dev, "Function not implemented, bailing.\n" );
return 0 ;
}
ret = funcs->clear_infoframe(connector, type);
if (ret) {
drm_dbg_kms(dev, "Call failed: %d\n" , ret);
return ret;
}
return 0 ;
}
static int clear_infoframe(struct drm_connector *connector,
struct drm_connector_hdmi_infoframe *old_frame)
{
int ret;
ret = clear_device_infoframe(connector, old_frame->data.any.type);
if (ret)
return ret;
return 0 ;
}
static int write_device_infoframe(struct drm_connector *connector,
union hdmi_infoframe *frame)
{
const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
struct drm_device *dev = connector->dev;
u8 buffer[HDMI_INFOFRAME_SIZE(MAX)];
int ret;
int len;
drm_dbg_kms(dev, "Writing infoframe type %x\n" , frame->any.type);
if (!funcs || !funcs->write_infoframe) {
drm_dbg_kms(dev, "Function not implemented, bailing.\n" );
return -EINVAL;
}
len = hdmi_infoframe_pack(frame, buffer, sizeof (buffer));
if (len < 0 )
return len;
ret = funcs->write_infoframe(connector, frame->any.type, buffer, len);
if (ret) {
drm_dbg_kms(dev, "Call failed: %d\n" , ret);
return ret;
}
return 0 ;
}
static int write_infoframe(struct drm_connector *connector,
struct drm_connector_hdmi_infoframe *new_frame)
{
int ret;
ret = write_device_infoframe(connector, &new_frame->data);
if (ret)
return ret;
return 0 ;
}
static int write_or_clear_infoframe(struct drm_connector *connector,
struct drm_connector_hdmi_infoframe *old_frame,
struct drm_connector_hdmi_infoframe *new_frame)
{
if (new_frame->set)
return write_infoframe(connector, new_frame);
if (old_frame->set && !new_frame->set)
return clear_infoframe(connector, old_frame);
return 0 ;
}
/**
* drm_atomic_helper_connector_hdmi_update_infoframes - Update the Infoframes
* @ connector : A pointer to the HDMI connector
* @ state : The HDMI connector state to generate the infoframe from
*
* This function is meant for HDMI connector drivers to write their
* infoframes . It will typically be used in a
* @ drm_connector_helper_funcs . atomic_enable implementation .
*
* Returns :
* Zero on success , error code on failure .
*/
int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector,
struct drm_atomic_state *state)
{
struct drm_connector_state *old_conn_state =
drm_atomic_get_old_connector_state(state, connector);
struct drm_connector_state *new_conn_state =
drm_atomic_get_new_connector_state(state, connector);
struct drm_display_info *info = &connector->display_info;
int ret;
if (!info->is_hdmi)
return 0 ;
mutex_lock(&connector->hdmi.infoframes.lock);
ret = write_or_clear_infoframe(connector,
&old_conn_state->hdmi.infoframes.avi,
&new_conn_state->hdmi.infoframes.avi);
if (ret)
goto out;
if (connector->hdmi.infoframes.audio.set) {
ret = write_infoframe(connector,
&connector->hdmi.infoframes.audio);
if (ret)
goto out;
}
ret = write_or_clear_infoframe(connector,
&old_conn_state->hdmi.infoframes.hdr_drm,
&new_conn_state->hdmi.infoframes.hdr_drm);
if (ret)
goto out;
ret = write_or_clear_infoframe(connector,
&old_conn_state->hdmi.infoframes.spd,
&new_conn_state->hdmi.infoframes.spd);
if (ret)
goto out;
if (info->has_hdmi_infoframe) {
ret = write_or_clear_infoframe(connector,
&old_conn_state->hdmi.infoframes.hdmi,
&new_conn_state->hdmi.infoframes.hdmi);
if (ret)
goto out;
}
out:
mutex_unlock(&connector->hdmi.infoframes.lock);
return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_infoframes);
/**
* drm_atomic_helper_connector_hdmi_update_audio_infoframe - Update the Audio Infoframe
* @ connector : A pointer to the HDMI connector
* @ frame : A pointer to the audio infoframe to write
*
* This function is meant for HDMI connector drivers to update their
* audio infoframe . It will typically be used in one of the ALSA hooks
* ( most likely prepare ) .
*
* Returns :
* Zero on success , error code on failure .
*/
int
drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector,
struct hdmi_audio_infoframe *frame)
{
struct drm_connector_hdmi_infoframe *infoframe =
&connector->hdmi.infoframes.audio;
struct drm_display_info *info = &connector->display_info;
int ret;
if (!info->is_hdmi)
return 0 ;
mutex_lock(&connector->hdmi.infoframes.lock);
memcpy(&infoframe->data, frame, sizeof (infoframe->data));
infoframe->set = true ;
ret = write_infoframe(connector, infoframe);
mutex_unlock(&connector->hdmi.infoframes.lock);
return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_audio_infoframe);
/**
* drm_atomic_helper_connector_hdmi_clear_audio_infoframe - Stop sending the Audio Infoframe
* @ connector : A pointer to the HDMI connector
*
* This function is meant for HDMI connector drivers to stop sending their
* audio infoframe . It will typically be used in one of the ALSA hooks
* ( most likely shutdown ) .
*
* Returns :
* Zero on success , error code on failure .
*/
int
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *connector)
{
struct drm_connector_hdmi_infoframe *infoframe =
&connector->hdmi.infoframes.audio;
struct drm_display_info *info = &connector->display_info;
int ret;
if (!info->is_hdmi)
return 0 ;
mutex_lock(&connector->hdmi.infoframes.lock);
infoframe->set = false ;
ret = clear_infoframe(connector, infoframe);
memset(&infoframe->data, 0 , sizeof (infoframe->data));
mutex_unlock(&connector->hdmi.infoframes.lock);
return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_clear_audio_infoframe);
static void
drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector,
enum drm_connector_status status)
{
const struct drm_edid *drm_edid;
if (status == connector_status_disconnected) {
// TODO: also handle scramber, HDMI sink disconnected.
drm_connector_hdmi_audio_plugged_notify(connector, false );
drm_edid_connector_update(connector, NULL);
drm_connector_cec_phys_addr_invalidate(connector);
return ;
}
if (connector->hdmi.funcs->read_edid)
drm_edid = connector->hdmi.funcs->read_edid(connector);
else
drm_edid = drm_edid_read(connector);
drm_edid_connector_update(connector, drm_edid);
drm_edid_free(drm_edid);
if (status == connector_status_connected) {
// TODO: also handle scramber, HDMI sink is now connected.
drm_connector_hdmi_audio_plugged_notify(connector, true );
drm_connector_cec_phys_addr_set(connector);
}
}
/**
* drm_atomic_helper_connector_hdmi_hotplug - Handle the hotplug event for the HDMI connector
* @ connector : A pointer to the HDMI connector
* @ status : Connection status
*
* This function should be called as a part of the . detect ( ) / . detect_ctx ( )
* callbacks for all status changes .
*/
void drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector,
enum drm_connector_status status)
{
drm_atomic_helper_connector_hdmi_update(connector, status);
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_hotplug);
/**
* drm_atomic_helper_connector_hdmi_force - HDMI Connector implementation of the force callback
* @ connector : A pointer to the HDMI connector
*
* This function implements the . force ( ) callback for the HDMI connectors . It
* can either be used directly as the callback or should be called from within
* the . force ( ) callback implementation to maintain the HDMI - specific
* connector ' s data .
*/
void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector)
{
drm_atomic_helper_connector_hdmi_update(connector, connector->status);
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_force);
Messung V0.5 in Prozent C=99 H=98 G=98
¤ Dauer der Verarbeitung: 0.34 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland