drm/bridge: dw-hdmi: add support for YUV420 output
authorNeil Armstrong <narmstrong@baylibre.com>
Fri, 1 Feb 2019 12:07:49 +0000 (12:07 +0000)
committerAndrzej Hajda <a.hajda@samsung.com>
Fri, 1 Feb 2019 12:15:10 +0000 (13:15 +0100)
In order to support the HDMI2.0 YUV420 display modes, this patch
adds support for the YUV420 TMDS Clock divided by 2 and the controller
passthrough mode.

YUV420 Synopsys PHY support will need some specific configuration table
to support theses modes.

This patch is based on work from Zheng Yang <zhengyang@rock-chips.com> in
the Rockchip Linux 4.4 BSP at [1]

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

Cc: Zheng Yang <zhengyang@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-5-git-send-email-narmstrong@baylibre.com
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c

index 6d5a2e9..a63e5f0 100644 (file)
@@ -99,6 +99,7 @@ struct hdmi_vmode {
        unsigned int mpixelclock;
        unsigned int mpixelrepetitioninput;
        unsigned int mpixelrepetitionoutput;
+       unsigned int mtmdsclock;
 };
 
 struct hdmi_data_info {
@@ -543,7 +544,7 @@ static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi)
 static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi)
 {
        mutex_lock(&hdmi->audio_mutex);
-       hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock,
+       hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock,
                                 hdmi->sample_rate);
        mutex_unlock(&hdmi->audio_mutex);
 }
@@ -552,7 +553,7 @@ void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate)
 {
        mutex_lock(&hdmi->audio_mutex);
        hdmi->sample_rate = rate;
-       hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock,
+       hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock,
                                 hdmi->sample_rate);
        mutex_unlock(&hdmi->audio_mutex);
 }
@@ -653,6 +654,20 @@ static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format)
        }
 }
 
+static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format)
+{
+       switch (bus_format) {
+       case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+       case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+       case MEDIA_BUS_FMT_UYYVYY12_0_5X36:
+       case MEDIA_BUS_FMT_UYYVYY16_0_5X48:
+               return true;
+
+       default:
+               return false;
+       }
+}
+
 static int hdmi_bus_fmt_color_depth(unsigned int bus_format)
 {
        switch (bus_format) {
@@ -882,7 +897,8 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
        u8 val, vp_conf;
 
        if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) ||
-           hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) {
+           hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) ||
+           hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) {
                switch (hdmi_bus_fmt_color_depth(
                                        hdmi->hdmi_data.enc_out_bus_format)) {
                case 8:
@@ -1036,7 +1052,7 @@ EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write);
  */
 void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi)
 {
-       unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mpixelclock;
+       unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock;
 
        /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */
        if (hdmi->connector.display_info.hdmi.scdc.supported) {
@@ -1198,6 +1214,8 @@ static int hdmi_phy_configure_dwc_hdmi_3d_tx(struct dw_hdmi *hdmi,
        const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr;
        const struct dw_hdmi_phy_config *phy_config = pdata->phy_config;
 
+       /* TOFIX Will need 420 specific PHY configuration tables */
+
        /* PLL/MPLL Cfg - always match on final entry */
        for (; mpll_config->mpixelclock != ~0UL; mpll_config++)
                if (mpixelclock <= mpll_config->mpixelclock)
@@ -1245,6 +1263,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
        const struct dw_hdmi_phy_data *phy = hdmi->phy.data;
        const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
        unsigned long mpixelclock = hdmi->hdmi_data.video_mode.mpixelclock;
+       unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock;
        int ret;
 
        dw_hdmi_phy_power_off(hdmi);
@@ -1273,7 +1292,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
        }
 
        /* Wait for resuming transmission of TMDS clock and data */
-       if (mpixelclock > HDMI14_MAX_TMDSCLK)
+       if (mtmdsclock > HDMI14_MAX_TMDSCLK)
                msleep(100);
 
        return dw_hdmi_phy_power_on(hdmi);
@@ -1390,6 +1409,8 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
                frame.colorspace = HDMI_COLORSPACE_YUV444;
        else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format))
                frame.colorspace = HDMI_COLORSPACE_YUV422;
+       else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
+               frame.colorspace = HDMI_COLORSPACE_YUV420;
        else
                frame.colorspace = HDMI_COLORSPACE_RGB;
 
@@ -1547,15 +1568,18 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
        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;
+       unsigned int vdisplay, hdisplay;
 
-       vmode->mpixelclock = mode->clock * 1000;
+       vmode->mtmdsclock = vmode->mpixelclock = mode->clock * 1000;
 
        dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
 
+       if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
+               vmode->mtmdsclock /= 2;
+
        /* Set up HDMI_FC_INVIDCONF */
        inv_val = (hdmi->hdmi_data.hdcp_enable ||
-                  vmode->mpixelclock > HDMI14_MAX_TMDSCLK ||
+                  vmode->mtmdsclock > HDMI14_MAX_TMDSCLK ||
                   hdmi_info->scdc.scrambling.low_rates ?
                HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
                HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
@@ -1589,6 +1613,22 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
 
        hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF);
 
+       hdisplay = mode->hdisplay;
+       hblank = mode->htotal - mode->hdisplay;
+       h_de_hs = mode->hsync_start - mode->hdisplay;
+       hsync_len = mode->hsync_end - mode->hsync_start;
+
+       /*
+        * When we're setting a YCbCr420 mode, we need
+        * to adjust the horizontal timing to suit.
+        */
+       if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) {
+               hdisplay /= 2;
+               hblank /= 2;
+               h_de_hs /= 2;
+               hsync_len /= 2;
+       }
+
        vdisplay = mode->vdisplay;
        vblank = mode->vtotal - mode->vdisplay;
        v_de_vs = mode->vsync_start - mode->vdisplay;
@@ -1607,7 +1647,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
 
        /* Scrambling Control */
        if (hdmi_info->scdc.supported) {
-               if (vmode->mpixelclock > HDMI14_MAX_TMDSCLK ||
+               if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK ||
                    hdmi_info->scdc.scrambling.low_rates) {
                        /*
                         * HDMI2.0 Specifies the following procedure:
@@ -1645,15 +1685,14 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
        }
 
        /* Set up horizontal active pixel width */
-       hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1);
-       hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0);
+       hdmi_writeb(hdmi, hdisplay >> 8, HDMI_FC_INHACTV1);
+       hdmi_writeb(hdmi, hdisplay, HDMI_FC_INHACTV0);
 
        /* Set up vertical active lines */
        hdmi_writeb(hdmi, vdisplay >> 8, HDMI_FC_INVACTV1);
        hdmi_writeb(hdmi, vdisplay, HDMI_FC_INVACTV0);
 
        /* Set up horizontal blanking pixel region width */
-       hblank = mode->htotal - mode->hdisplay;
        hdmi_writeb(hdmi, hblank >> 8, HDMI_FC_INHBLANK1);
        hdmi_writeb(hdmi, hblank, HDMI_FC_INHBLANK0);
 
@@ -1661,7 +1700,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
        hdmi_writeb(hdmi, vblank, HDMI_FC_INVBLANK);
 
        /* Set up HSYNC active edge delay width (in pixel clks) */
-       h_de_hs = mode->hsync_start - mode->hdisplay;
        hdmi_writeb(hdmi, h_de_hs >> 8, HDMI_FC_HSYNCINDELAY1);
        hdmi_writeb(hdmi, h_de_hs, HDMI_FC_HSYNCINDELAY0);
 
@@ -1669,7 +1707,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
        hdmi_writeb(hdmi, v_de_vs, HDMI_FC_VSYNCINDELAY);
 
        /* Set up HSYNC active pulse width (in pixel clks) */
-       hsync_len = mode->hsync_end - mode->hsync_start;
        hdmi_writeb(hdmi, hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1);
        hdmi_writeb(hdmi, hsync_len, HDMI_FC_HSYNCINWIDTH0);