drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling support
authorNeil Armstrong <narmstrong@baylibre.com>
Fri, 1 Feb 2019 12:07:46 +0000 (12:07 +0000)
committerAndrzej Hajda <a.hajda@samsung.com>
Fri, 1 Feb 2019 12:15:07 +0000 (13:15 +0100)
Add support for SCDC Setup for TMDS Clock > 3.4GHz and enable TMDS
Scrambling when supported or mandatory.

This patch also adds an helper to setup the control bit to support
the high TMDS Bit Period/TMDS Clock-Period Ratio as required with
TMDS Clock > 3.4GHz for HDMI2.0 3840x2160@60/50 modes.

These changes were based on work done by Huicong Xu <xhc@rock-chips.com>
and Nickey Yang <nickey.yang@rock-chips.com> to support HDMI2.0 modes
on the Rockchip 4.4 BSP kernel at [1]

[1] https://github.com/rockchip-linux/kernel/tree/release-4.4

Cc: Nickey Yang <nickey.yang@rock-chips.com>
Cc: Huicong Xu <xhc@rock-chips.com>
Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
Tested-by: Heiko Stuebner <heiko@sntech.de>
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>
Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
Link: https://patchwork.freedesktop.org/patch/msgid/1549022873-40549-2-git-send-email-narmstrong@baylibre.com
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
include/drm/bridge/dw_hdmi.h

index 7aae726..6d5a2e9 100644 (file)
@@ -27,6 +27,7 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_encoder_slave.h>
+#include <drm/drm_scdc_helper.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/bridge/dw_hdmi.h>
 
 
 #define HDMI_EDID_LEN          512
 
+/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */
+#define SCDC_MIN_SOURCE_VERSION        0x1
+
+#define HDMI14_MAX_TMDSCLK     340000000
+
 enum hdmi_datamap {
        RGB444_8B = 0x01,
        RGB444_10B = 0x03,
@@ -1015,6 +1021,33 @@ void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write);
 
+/*
+ * HDMI2.0 Specifies the following procedure for High TMDS Bit Rates:
+ * - The Source shall suspend transmission of the TMDS clock and data
+ * - The Source shall write to the TMDS_Bit_Clock_Ratio bit to change it
+ * from a 0 to a 1 or from a 1 to a 0
+ * - The Source shall allow a minimum of 1 ms and a maximum of 100 ms from
+ * the time the TMDS_Bit_Clock_Ratio bit is written until resuming
+ * transmission of TMDS clock and data
+ *
+ * To respect the 100ms maximum delay, the dw_hdmi_set_high_tmds_clock_ratio()
+ * helper should called right before enabling the TMDS Clock and Data in
+ * the PHY configuration callback.
+ */
+void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi)
+{
+       unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mpixelclock;
+
+       /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */
+       if (hdmi->connector.display_info.hdmi.scdc.supported) {
+               if (mtmdsclock > HDMI14_MAX_TMDSCLK)
+                       drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1);
+               else
+                       drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 0);
+       }
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_high_tmds_clock_ratio);
+
 static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable)
 {
        hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0,
@@ -1216,6 +1249,8 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
 
        dw_hdmi_phy_power_off(hdmi);
 
+       dw_hdmi_set_high_tmds_clock_ratio(hdmi);
+
        /* Leave low power consumption mode by asserting SVSRET. */
        if (phy->has_svsret)
                dw_hdmi_phy_enable_svsret(hdmi, 1);
@@ -1237,6 +1272,10 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
                return ret;
        }
 
+       /* Wait for resuming transmission of TMDS clock and data */
+       if (mpixelclock > HDMI14_MAX_TMDSCLK)
+               msleep(100);
+
        return dw_hdmi_phy_power_on(hdmi);
 }
 
@@ -1504,7 +1543,8 @@ static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi,
 static void hdmi_av_composer(struct dw_hdmi *hdmi,
                             const struct drm_display_mode *mode)
 {
-       u8 inv_val;
+       u8 inv_val, bytes;
+       struct drm_hdmi_info *hdmi_info = &hdmi->connector.display_info.hdmi;
        struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
        int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len;
        unsigned int vdisplay;
@@ -1514,7 +1554,9 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
        dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
 
        /* Set up HDMI_FC_INVIDCONF */
-       inv_val = (hdmi->hdmi_data.hdcp_enable ?
+       inv_val = (hdmi->hdmi_data.hdcp_enable ||
+                  vmode->mpixelclock > HDMI14_MAX_TMDSCLK ||
+                  hdmi_info->scdc.scrambling.low_rates ?
                HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
                HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
 
@@ -1563,6 +1605,45 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
                vsync_len /= 2;
        }
 
+       /* Scrambling Control */
+       if (hdmi_info->scdc.supported) {
+               if (vmode->mpixelclock > HDMI14_MAX_TMDSCLK ||
+                   hdmi_info->scdc.scrambling.low_rates) {
+                       /*
+                        * HDMI2.0 Specifies the following procedure:
+                        * After the Source Device has determined that
+                        * SCDC_Present is set (=1), the Source Device should
+                        * write the accurate Version of the Source Device
+                        * to the Source Version field in the SCDCS.
+                        * Source Devices compliant shall set the
+                        * Source Version = 1.
+                        */
+                       drm_scdc_readb(&hdmi->i2c->adap, SCDC_SINK_VERSION,
+                                      &bytes);
+                       drm_scdc_writeb(&hdmi->i2c->adap, SCDC_SOURCE_VERSION,
+                               min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION));
+
+                       /* Enabled Scrambling in the Sink */
+                       drm_scdc_set_scrambling(&hdmi->i2c->adap, 1);
+
+                       /*
+                        * To activate the scrambler feature, you must ensure
+                        * that the quasi-static configuration bit
+                        * fc_invidconf.HDCP_keepout is set at configuration
+                        * time, before the required mc_swrstzreq.tmdsswrst_req
+                        * reset request is issued.
+                        */
+                       hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ,
+                                   HDMI_MC_SWRSTZ);
+                       hdmi_writeb(hdmi, 1, HDMI_FC_SCRAMBLER_CTRL);
+               } else {
+                       hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL);
+                       hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ,
+                                   HDMI_MC_SWRSTZ);
+                       drm_scdc_set_scrambling(&hdmi->i2c->adap, 0);
+               }
+       }
+
        /* Set up horizontal active pixel width */
        hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1);
        hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0);
index 9d90eb9..3f3c616 100644 (file)
 #define HDMI_FC_MASK2                           0x10DA
 #define HDMI_FC_POL2                            0x10DB
 #define HDMI_FC_PRCONF                          0x10E0
+#define HDMI_FC_SCRAMBLER_CTRL                  0x10E1
 
 #define HDMI_FC_GMD_STAT                        0x1100
 #define HDMI_FC_GMD_EN                          0x1101
index 9f93895..66e7077 100644 (file)
@@ -159,6 +159,7 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
 void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate);
 void dw_hdmi_audio_enable(struct dw_hdmi *hdmi);
 void dw_hdmi_audio_disable(struct dw_hdmi *hdmi);
+void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi);
 
 /* PHY configuration */
 void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address);