|
@@ -36,6 +36,7 @@
|
|
|
#include <drm/drm_dp_helper.h>
|
|
|
#include <drm/drm_fb_helper.h>
|
|
|
#include <drm/drm_plane_helper.h>
|
|
|
+#include <drm/drm_scdc_helper.h>
|
|
|
#include <drm/drm_edid.h>
|
|
|
|
|
|
#include <nvif/class.h>
|
|
@@ -531,6 +532,7 @@ nv50_hdmi_disable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)
|
|
|
static void
|
|
|
nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
|
|
|
{
|
|
|
+ struct nouveau_drm *drm = nouveau_drm(encoder->dev);
|
|
|
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
|
|
|
struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
|
|
|
struct nv50_disp *disp = nv50_disp(encoder->dev);
|
|
@@ -548,9 +550,12 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
|
|
|
.pwr.rekey = 56, /* binary driver, and tegra, constant */
|
|
|
};
|
|
|
struct nouveau_connector *nv_connector;
|
|
|
+ struct drm_hdmi_info *hdmi;
|
|
|
u32 max_ac_packet;
|
|
|
union hdmi_infoframe avi_frame;
|
|
|
union hdmi_infoframe vendor_frame;
|
|
|
+ bool scdc_supported, high_tmds_clock_ratio = false, scrambling = false;
|
|
|
+ u8 config;
|
|
|
int ret;
|
|
|
int size;
|
|
|
|
|
@@ -558,8 +563,11 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
|
|
|
if (!drm_detect_hdmi_monitor(nv_connector->edid))
|
|
|
return;
|
|
|
|
|
|
+ hdmi = &nv_connector->base.display_info.hdmi;
|
|
|
+ scdc_supported = hdmi->scdc.supported;
|
|
|
+
|
|
|
ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame.avi, mode,
|
|
|
- false);
|
|
|
+ scdc_supported);
|
|
|
if (!ret) {
|
|
|
/* We have an AVI InfoFrame, populate it to the display */
|
|
|
args.pwr.avi_infoframe_length
|
|
@@ -582,12 +590,42 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
|
|
|
max_ac_packet -= 18; /* constant from tegra */
|
|
|
args.pwr.max_ac_packet = max_ac_packet / 32;
|
|
|
|
|
|
+ if (hdmi->scdc.scrambling.supported) {
|
|
|
+ high_tmds_clock_ratio = mode->clock > 340000;
|
|
|
+ scrambling = high_tmds_clock_ratio ||
|
|
|
+ hdmi->scdc.scrambling.low_rates;
|
|
|
+ }
|
|
|
+
|
|
|
+ args.pwr.scdc =
|
|
|
+ NV50_DISP_SOR_HDMI_PWR_V0_SCDC_SCRAMBLE * scrambling |
|
|
|
+ NV50_DISP_SOR_HDMI_PWR_V0_SCDC_DIV_BY_4 * high_tmds_clock_ratio;
|
|
|
+
|
|
|
size = sizeof(args.base)
|
|
|
+ sizeof(args.pwr)
|
|
|
+ args.pwr.avi_infoframe_length
|
|
|
+ args.pwr.vendor_infoframe_length;
|
|
|
nvif_mthd(&disp->disp->object, 0, &args, size);
|
|
|
+
|
|
|
nv50_audio_enable(encoder, mode);
|
|
|
+
|
|
|
+ /* If SCDC is supported by the downstream monitor, update
|
|
|
+ * divider / scrambling settings to what we programmed above.
|
|
|
+ */
|
|
|
+ if (!hdmi->scdc.scrambling.supported)
|
|
|
+ return;
|
|
|
+
|
|
|
+ ret = drm_scdc_readb(nv_encoder->i2c, SCDC_TMDS_CONFIG, &config);
|
|
|
+ if (ret < 0) {
|
|
|
+ NV_ERROR(drm, "Failure to read SCDC_TMDS_CONFIG: %d\n", ret);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ config &= ~(SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 | SCDC_SCRAMBLING_ENABLE);
|
|
|
+ config |= SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 * high_tmds_clock_ratio;
|
|
|
+ config |= SCDC_SCRAMBLING_ENABLE * scrambling;
|
|
|
+ ret = drm_scdc_writeb(nv_encoder->i2c, SCDC_TMDS_CONFIG, config);
|
|
|
+ if (ret < 0)
|
|
|
+ NV_ERROR(drm, "Failure to write SCDC_TMDS_CONFIG = 0x%02x: %d\n",
|
|
|
+ config, ret);
|
|
|
}
|
|
|
|
|
|
/******************************************************************************
|