Merge drm/drm-next into drm-intel-next
Sync with v6.11-rc1 in general, and specifically get the new BACKLIGHT_POWER_ constants for power states. Signed-off-by: Jani Nikula <jani.nikula@intel.com>
This commit is contained in:
@@ -70,3 +70,10 @@ config DRM_DISPLAY_HDMI_HELPER
|
||||
depends on DRM_DISPLAY_HELPER
|
||||
help
|
||||
DRM display helpers for HDMI.
|
||||
|
||||
config DRM_DISPLAY_HDMI_STATE_HELPER
|
||||
bool
|
||||
depends on DRM_DISPLAY_HELPER
|
||||
select DRM_DISPLAY_HDMI_HELPER
|
||||
help
|
||||
DRM KMS state helpers for HDMI.
|
||||
|
||||
@@ -14,6 +14,8 @@ drm_display_helper-$(CONFIG_DRM_DISPLAY_HDCP_HELPER) += drm_hdcp_helper.o
|
||||
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_HELPER) += \
|
||||
drm_hdmi_helper.o \
|
||||
drm_scdc_helper.o
|
||||
drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_STATE_HELPER) += \
|
||||
drm_hdmi_state_helper.o
|
||||
drm_display_helper-$(CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
|
||||
drm_display_helper-$(CONFIG_DRM_DISPLAY_DP_AUX_CEC) += drm_dp_cec.o
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ struct dp_aux_ep_device_with_data {
|
||||
*
|
||||
* Return: True if this driver matches this device; false otherwise.
|
||||
*/
|
||||
static int dp_aux_ep_match(struct device *dev, struct device_driver *drv)
|
||||
static int dp_aux_ep_match(struct device *dev, const struct device_driver *drv)
|
||||
{
|
||||
return !!of_match_device(drv->of_match_table, dev);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <drm/display/drm_dp_helper.h>
|
||||
#include <drm/display/drm_dp_mst_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_fixed.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_vblank.h>
|
||||
#include <drm/drm_panel.h>
|
||||
@@ -4151,9 +4152,9 @@ int drm_dp_bw_overhead(int lane_count, int hactive,
|
||||
int symbol_cycles;
|
||||
|
||||
if (lane_count == 0 || hactive == 0 || bpp_x16 == 0) {
|
||||
DRM_DEBUG_KMS("Invalid BW overhead params: lane_count %d, hactive %d, bpp_x16 %d.%04d\n",
|
||||
DRM_DEBUG_KMS("Invalid BW overhead params: lane_count %d, hactive %d, bpp_x16 " FXP_Q4_FMT "\n",
|
||||
lane_count, hactive,
|
||||
bpp_x16 >> 4, (bpp_x16 & 0xf) * 625);
|
||||
FXP_Q4_ARGS(bpp_x16));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2931,7 +2931,7 @@ static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
|
||||
|
||||
/* FIXME: Actually do some real error handling here */
|
||||
ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
|
||||
if (ret <= 0) {
|
||||
if (ret < 0) {
|
||||
drm_err(mgr->dev, "Sending link address failed with %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
@@ -2983,7 +2983,7 @@ static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
|
||||
mutex_unlock(&mgr->lock);
|
||||
|
||||
out:
|
||||
if (ret <= 0)
|
||||
if (ret < 0)
|
||||
mstb->link_address_sent = false;
|
||||
kfree(txmsg);
|
||||
return ret < 0 ? ret : changed;
|
||||
@@ -4098,6 +4098,7 @@ static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr)
|
||||
if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) {
|
||||
const struct drm_dp_connection_status_notify *conn_stat =
|
||||
&up_req->msg.u.conn_stat;
|
||||
bool handle_csn;
|
||||
|
||||
drm_dbg_kms(mgr->dev, "Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n",
|
||||
conn_stat->port_number,
|
||||
@@ -4106,6 +4107,16 @@ static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr)
|
||||
conn_stat->message_capability_status,
|
||||
conn_stat->input_port,
|
||||
conn_stat->peer_device_type);
|
||||
|
||||
mutex_lock(&mgr->probe_lock);
|
||||
handle_csn = mgr->mst_primary->link_address_sent;
|
||||
mutex_unlock(&mgr->probe_lock);
|
||||
|
||||
if (!handle_csn) {
|
||||
drm_dbg_kms(mgr->dev, "Got CSN before finish topology probing. Skip it.");
|
||||
kfree(up_req);
|
||||
goto out;
|
||||
}
|
||||
} else if (up_req->msg.req_type == DP_RESOURCE_STATUS_NOTIFY) {
|
||||
const struct drm_dp_resource_status_notify *res_stat =
|
||||
&up_req->msg.u.resource_stat;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <drm/display/drm_dp_helper.h>
|
||||
#include <drm/display/drm_dsc_helper.h>
|
||||
#include <drm/drm_fixed.h>
|
||||
#include <drm/drm_print.h>
|
||||
|
||||
/**
|
||||
@@ -1472,3 +1473,93 @@ u32 drm_dsc_flatness_det_thresh(const struct drm_dsc_config *dsc)
|
||||
return 2 << (dsc->bits_per_component - 8);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dsc_flatness_det_thresh);
|
||||
|
||||
static void drm_dsc_dump_config_main_params(struct drm_printer *p, int indent,
|
||||
const struct drm_dsc_config *cfg)
|
||||
{
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: version: %d.%d, picture: w=%d, h=%d, slice: count=%d, w=%d, h=%d, size=%d\n",
|
||||
cfg->dsc_version_major, cfg->dsc_version_minor,
|
||||
cfg->pic_width, cfg->pic_height,
|
||||
cfg->slice_count, cfg->slice_width, cfg->slice_height, cfg->slice_chunk_size);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: mode: block-pred=%s, vbr=%s, rgb=%s, simple-422=%s, native-422=%s, native-420=%s\n",
|
||||
str_yes_no(cfg->block_pred_enable), str_yes_no(cfg->vbr_enable),
|
||||
str_yes_no(cfg->convert_rgb),
|
||||
str_yes_no(cfg->simple_422), str_yes_no(cfg->native_422), str_yes_no(cfg->native_420));
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: color-depth: uncompressed-bpc=%d, compressed-bpp=" FXP_Q4_FMT " line-buf-bpp=%d\n",
|
||||
cfg->bits_per_component, FXP_Q4_ARGS(cfg->bits_per_pixel), cfg->line_buf_depth);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: rc-model: size=%d, bits=%d, mux-word-size: %d, initial-delays: xmit=%d, dec=%d\n",
|
||||
cfg->rc_model_size, cfg->rc_bits, cfg->mux_word_size,
|
||||
cfg->initial_xmit_delay, cfg->initial_dec_delay);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: offsets: initial=%d, final=%d, slice-bpg=%d\n",
|
||||
cfg->initial_offset, cfg->final_offset, cfg->slice_bpg_offset);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: line-bpg-offsets: first=%d, non-first=%d, second=%d, non-second=%d, second-adj=%d\n",
|
||||
cfg->first_line_bpg_offset, cfg->nfl_bpg_offset,
|
||||
cfg->second_line_bpg_offset, cfg->nsl_bpg_offset, cfg->second_line_offset_adj);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: rc-tgt-offsets: low=%d, high=%d, rc-edge-factor: %d, rc-quant-incr-limits: [0]=%d, [1]=%d\n",
|
||||
cfg->rc_tgt_offset_low, cfg->rc_tgt_offset_high,
|
||||
cfg->rc_edge_factor, cfg->rc_quant_incr_limit0, cfg->rc_quant_incr_limit1);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: initial-scale: %d, scale-intervals: increment=%d, decrement=%d\n",
|
||||
cfg->initial_scale_value, cfg->scale_increment_interval, cfg->scale_decrement_interval);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: flatness: min-qp=%d, max-qp=%d\n",
|
||||
cfg->flatness_min_qp, cfg->flatness_max_qp);
|
||||
}
|
||||
|
||||
static void drm_dsc_dump_config_rc_params(struct drm_printer *p, int indent,
|
||||
const struct drm_dsc_config *cfg)
|
||||
{
|
||||
const u16 *bt = cfg->rc_buf_thresh;
|
||||
const struct drm_dsc_rc_range_parameters *rp = cfg->rc_range_params;
|
||||
|
||||
BUILD_BUG_ON(ARRAY_SIZE(cfg->rc_buf_thresh) != 14);
|
||||
BUILD_BUG_ON(ARRAY_SIZE(cfg->rc_range_params) != 15);
|
||||
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: rc-level: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14\n");
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: rc-buf-thresh: %3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d\n",
|
||||
bt[0], bt[1], bt[2], bt[3], bt[4], bt[5], bt[6], bt[7],
|
||||
bt[8], bt[9], bt[10], bt[11], bt[12], bt[13]);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: rc-min-qp: %3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d\n",
|
||||
rp[0].range_min_qp, rp[1].range_min_qp, rp[2].range_min_qp, rp[3].range_min_qp,
|
||||
rp[4].range_min_qp, rp[5].range_min_qp, rp[6].range_min_qp, rp[7].range_min_qp,
|
||||
rp[8].range_min_qp, rp[9].range_min_qp, rp[10].range_min_qp, rp[11].range_min_qp,
|
||||
rp[12].range_min_qp, rp[13].range_min_qp, rp[14].range_min_qp);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: rc-max-qp: %3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d\n",
|
||||
rp[0].range_max_qp, rp[1].range_max_qp, rp[2].range_max_qp, rp[3].range_max_qp,
|
||||
rp[4].range_max_qp, rp[5].range_max_qp, rp[6].range_max_qp, rp[7].range_max_qp,
|
||||
rp[8].range_max_qp, rp[9].range_max_qp, rp[10].range_max_qp, rp[11].range_max_qp,
|
||||
rp[12].range_max_qp, rp[13].range_max_qp, rp[14].range_max_qp);
|
||||
drm_printf_indent(p, indent,
|
||||
"dsc-cfg: rc-bpg-offset: %3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d\n",
|
||||
rp[0].range_bpg_offset, rp[1].range_bpg_offset, rp[2].range_bpg_offset, rp[3].range_bpg_offset,
|
||||
rp[4].range_bpg_offset, rp[5].range_bpg_offset, rp[6].range_bpg_offset, rp[7].range_bpg_offset,
|
||||
rp[8].range_bpg_offset, rp[9].range_bpg_offset, rp[10].range_bpg_offset, rp[11].range_bpg_offset,
|
||||
rp[12].range_bpg_offset, rp[13].range_bpg_offset, rp[14].range_bpg_offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_dsc_dump_config - Dump the provided DSC configuration
|
||||
* @p: The printer used for output
|
||||
* @indent: Tab indentation level (max 5)
|
||||
* @cfg: DSC configuration to print
|
||||
*
|
||||
* Print the provided DSC configuration in @cfg.
|
||||
*/
|
||||
void drm_dsc_dump_config(struct drm_printer *p, int indent,
|
||||
const struct drm_dsc_config *cfg)
|
||||
{
|
||||
drm_dsc_dump_config_main_params(p, indent, cfg);
|
||||
drm_dsc_dump_config_rc_params(p, indent, cfg);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dsc_dump_config);
|
||||
|
||||
@@ -195,3 +195,64 @@ void drm_hdmi_avi_infoframe_content_type(struct hdmi_avi_infoframe *frame,
|
||||
frame->itc = conn_state->content_type != DRM_MODE_CONTENT_TYPE_NO_DATA;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_hdmi_avi_infoframe_content_type);
|
||||
|
||||
/**
|
||||
* drm_hdmi_compute_mode_clock() - Computes the TMDS Character Rate
|
||||
* @mode: Display mode to compute the clock for
|
||||
* @bpc: Bits per character
|
||||
* @fmt: Output Pixel Format used
|
||||
*
|
||||
* Returns the TMDS Character Rate for a given mode, bpc count and output format.
|
||||
*
|
||||
* RETURNS:
|
||||
* The TMDS Character Rate, in Hertz, or 0 on error.
|
||||
*/
|
||||
unsigned long long
|
||||
drm_hdmi_compute_mode_clock(const struct drm_display_mode *mode,
|
||||
unsigned int bpc, enum hdmi_colorspace fmt)
|
||||
{
|
||||
unsigned long long clock = mode->clock * 1000ULL;
|
||||
unsigned int vic = drm_match_cea_mode(mode);
|
||||
|
||||
/*
|
||||
* CTA-861-G Spec, section 5.4 - Color Coding and Quantization
|
||||
* mandates that VIC 1 always uses 8 bpc.
|
||||
*/
|
||||
if (vic == 1 && bpc != 8)
|
||||
return 0;
|
||||
|
||||
if (fmt == HDMI_COLORSPACE_YUV422) {
|
||||
/*
|
||||
* HDMI 1.0 Spec, section 6.5 - Pixel Encoding states that
|
||||
* YUV422 sends 24 bits over three channels, with Cb and Cr
|
||||
* components being sent on odd and even pixels, respectively.
|
||||
*
|
||||
* If fewer than 12 bpc are sent, data are left justified.
|
||||
*/
|
||||
if (bpc > 12)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* HDMI 1.0 Spec, section 6.5 - Pixel Encoding
|
||||
* specifies that YUV422 sends two 12-bits components over
|
||||
* three TMDS channels per pixel clock, which is equivalent to
|
||||
* three 8-bits components over three channels used by RGB as
|
||||
* far as the clock rate goes.
|
||||
*/
|
||||
bpc = 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* HDMI 2.0 Spec, Section 7.1 - YCbCr 4:2:0 Pixel Encoding
|
||||
* specifies that YUV420 encoding is carried at a TMDS Character Rate
|
||||
* equal to half the pixel clock rate.
|
||||
*/
|
||||
if (fmt == HDMI_COLORSPACE_YUV420)
|
||||
clock = clock / 2;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
||||
clock = clock * 2;
|
||||
|
||||
return DIV_ROUND_CLOSEST_ULL(clock * bpc, 8);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_hdmi_compute_mode_clock);
|
||||
|
||||
@@ -0,0 +1,752 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_connector.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_print.h>
|
||||
|
||||
#include <drm/display/drm_hdmi_helper.h>
|
||||
#include <drm/display/drm_hdmi_state_helper.h>
|
||||
|
||||
/**
|
||||
* __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
|
||||
* 640x480 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;
|
||||
}
|
||||
|
||||
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:
|
||||
/* TODO: YUV420 is unsupported at the moment. */
|
||||
drm_dbg_kms(dev, "YUV420 format isn't supported yet.\n");
|
||||
return false;
|
||||
|
||||
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\n",
|
||||
drm_hdmi_connector_get_output_format_name(fmt));
|
||||
|
||||
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 (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(const struct drm_connector *connector,
|
||||
struct drm_connector_state *conn_state,
|
||||
const struct drm_display_mode *mode,
|
||||
unsigned int bpc)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
|
||||
/*
|
||||
* TODO: Add support for YCbCr420 output for HDMI 2.0 capable
|
||||
* devices, for modes that only support YCbCr420.
|
||||
*/
|
||||
if (hdmi_try_format_bpc(connector, conn_state, mode, bpc, HDMI_COLORSPACE_RGB)) {
|
||||
conn_state->hdmi.output_format = HDMI_COLORSPACE_RGB;
|
||||
return 0;
|
||||
}
|
||||
|
||||
drm_dbg_kms(dev, "Failed. No Format Supported for that bpc count.\n");
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int
|
||||
hdmi_compute_config(const struct drm_connector *connector,
|
||||
struct drm_connector_state *conn_state,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
unsigned int max_bpc = clamp_t(unsigned int,
|
||||
conn_state->max_bpc,
|
||||
8, connector->max_bpc);
|
||||
unsigned int bpc;
|
||||
int ret;
|
||||
|
||||
for (bpc = max_bpc; bpc >= 8; bpc -= 2) {
|
||||
drm_dbg_kms(dev, "Trying with a %d bpc output\n", bpc);
|
||||
|
||||
ret = hdmi_compute_format(connector, conn_state, mode, bpc);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
conn_state->hdmi.output_bpc = bpc;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
new_conn_state->hdmi.is_limited_range = hdmi_is_limited_range(connector, new_conn_state);
|
||||
|
||||
ret = hdmi_compute_config(connector, new_conn_state, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
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);
|
||||
|
||||
#define HDMI_MAX_INFOFRAME_SIZE 29
|
||||
|
||||
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_MAX_INFOFRAME_SIZE];
|
||||
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);
|
||||
Reference in New Issue
Block a user