drm: bridge: dw-hdmi: enable 3d mode
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / bridge / dw-hdmi.c
index 40e183d8806838d71f5b3b74436ff1afd089aed3..feef9da69c61577060a721096dcb91f5bd84b9c4 100644 (file)
@@ -29,6 +29,7 @@
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_encoder_slave.h>
+#include <drm/drm_scdc_helper.h>
 #include <drm/bridge/dw_hdmi.h>
 #ifdef CONFIG_SWITCH
 #include <linux/switch.h>
 #include "dw-hdmi-audio.h"
 
 #define HDMI_EDID_LEN          512
+#define DDC_SEGMENT_ADDR       0x30
 
 #define RGB                    0
 #define YCBCR444               1
 #define YCBCR422_16BITS                2
 #define YCBCR422_8BITS         3
 #define XVYCC444               4
+#define YCBCR420               5
 
 enum hdmi_datamap {
        RGB444_8B = 0x01,
@@ -172,6 +175,7 @@ struct dw_hdmi_i2c {
 
        u8                      slave_reg;
        bool                    is_regaddr;
+       bool                    is_segment;
 };
 
 struct dw_hdmi {
@@ -219,6 +223,7 @@ struct dw_hdmi {
 #ifdef CONFIG_SWITCH
        struct switch_dev switchdev;
 #endif
+       int irq;
 
        void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
        u8 (*read)(struct dw_hdmi *hdmi, int offset);
@@ -315,8 +320,12 @@ static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
                reinit_completion(&i2c->cmp);
 
                hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
-               hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
-                           HDMI_I2CM_OPERATION);
+               if (i2c->is_segment)
+                       hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ_EXT,
+                                   HDMI_I2CM_OPERATION);
+               else
+                       hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
+                                   HDMI_I2CM_OPERATION);
 
                stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
                if (!stat)
@@ -328,6 +337,7 @@ static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
 
                *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
        }
+       i2c->is_segment = false;
 
        return 0;
 }
@@ -377,12 +387,6 @@ static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
        dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr);
 
        for (i = 0; i < num; i++) {
-               if (msgs[i].addr != addr) {
-                       dev_warn(hdmi->dev,
-                                "unsupported transfer, changed slave address\n");
-                       return -EOPNOTSUPP;
-               }
-
                if (msgs[i].len == 0) {
                        dev_dbg(hdmi->dev,
                                "unsupported transfer %d/%d, no data\n",
@@ -401,15 +405,24 @@ static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
        /* Set slave device register address on transfer */
        i2c->is_regaddr = false;
 
+       /* Set segment pointer for I2C extended read mode operation */
+       i2c->is_segment = false;
+
        for (i = 0; i < num; i++) {
                dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n",
                        i + 1, num, msgs[i].len, msgs[i].flags);
-
-               if (msgs[i].flags & I2C_M_RD)
-                       ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, msgs[i].len);
-               else
-                       ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, msgs[i].len);
-
+               if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) {
+                       i2c->is_segment = true;
+                       hdmi_writeb(hdmi, DDC_SEGMENT_ADDR, HDMI_I2CM_SEGADDR);
+                       hdmi_writeb(hdmi, *msgs[i].buf, HDMI_I2CM_SEGPTR);
+               } else {
+                       if (msgs[i].flags & I2C_M_RD)
+                               ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf,
+                                                      msgs[i].len);
+                       else
+                               ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf,
+                                                       msgs[i].len);
+               }
                if (ret < 0)
                        break;
        }
@@ -705,7 +718,8 @@ static void hdmi_video_sample(struct dw_hdmi *hdmi)
                        color_format = 0x07;
                else
                        return;
-       } else if (hdmi->hdmi_data.enc_in_format == YCBCR444) {
+       } else if (hdmi->hdmi_data.enc_in_format == YCBCR444 ||
+                  hdmi->hdmi_data.enc_in_format == YCBCR420) {
                if (hdmi->hdmi_data.enc_color_depth == 8)
                        color_format = 0x09;
                else if (hdmi->hdmi_data.enc_color_depth == 10)
@@ -856,7 +870,8 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
        u8 val, vp_conf;
 
        if (hdmi_data->enc_out_format == RGB ||
-           hdmi_data->enc_out_format == YCBCR444) {
+           hdmi_data->enc_out_format == YCBCR444 ||
+           hdmi_data->enc_out_format == YCBCR420) {
                if (!hdmi_data->enc_color_depth) {
                        output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
                } else if (hdmi_data->enc_color_depth == 8) {
@@ -1065,7 +1080,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
                              unsigned char res, int cscon)
 {
        unsigned res_idx;
-       u8 val, msec;
+       u8 val, msec, tmds_cfg;
        const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
        const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg;
        const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr;
@@ -1127,6 +1142,16 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
        /* gen2 pddq */
        dw_hdmi_phy_gen2_pddq(hdmi, 1);
 
+       /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */
+       if (hdmi->connector.scdc_present) {
+               drm_scdc_readb(hdmi->ddc, SCDC_TMDS_CONFIG, &tmds_cfg);
+               if (mpll_config->mpixelclock > 340000000)
+                       tmds_cfg |= 2;
+               else
+                       tmds_cfg &= 0x1;
+               drm_scdc_writeb(hdmi->ddc, SCDC_TMDS_CONFIG, tmds_cfg);
+       }
+
        /* PHY reset */
        hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
        hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ);
@@ -1137,8 +1162,17 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
        hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
                    HDMI_PHY_I2CM_SLAVE_ADDR);
        hdmi_phy_test_clear(hdmi, 0);
-
-       hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce, 0x06);
+       /*
+        * RK3399 mpll clock source is vpll, also is vop clock source.
+        * vpll rate is twice of mpixelclock in YCBCR420 mode, we need
+        * to enable mpll pre-divider.
+        */
+       if (hdmi->hdmi_data.enc_in_format == YCBCR420 &&
+           hdmi->dev_type == RK3399_HDMI)
+               hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce | 4,
+                                  0x06);
+       else
+               hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce, 0x06);
        hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].gmp, 0x15);
 
        /* CURRCTRL */
@@ -1156,8 +1190,14 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
 
        dw_hdmi_phy_enable_powerdown(hdmi, false);
 
-       /* toggle TMDS enable */
+       /* toggle TMDS disable */
        dw_hdmi_phy_enable_tmds(hdmi, 0);
+
+       /* Wait for resuming transmission of TMDS clock and data */
+       if (mpll_config->mpixelclock > 340000000)
+               msleep(100);
+
+       /* toggle TMDS enable */
        dw_hdmi_phy_enable_tmds(hdmi, 1);
 
        /* gen2 tx power on */
@@ -1167,7 +1207,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
        if (is_rockchip(hdmi->dev_type))
                dw_hdmi_phy_enable_spare(hdmi, 1);
 
-       /*Wait for PHY PLL lock */
+       /* Wait for PHY PLL lock */
        msec = 5;
        do {
                val = hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
@@ -1242,6 +1282,8 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
                frame.colorspace = HDMI_COLORSPACE_YUV444;
        else if (hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS)
                frame.colorspace = HDMI_COLORSPACE_YUV422;
+       else if (hdmi->hdmi_data.enc_out_format == YCBCR420)
+               frame.colorspace = HDMI_COLORSPACE_YUV420;
        else
                frame.colorspace = HDMI_COLORSPACE_RGB;
 
@@ -1271,10 +1313,10 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
         */
 
        /*
-        * AVI data byte 1 differences: Colorspace in bits 4,5 rather than 5,6,
-        * active aspect present in bit 6 rather than 4.
+        * AVI data byte 1 differences: Colorspace in bits 0,1,7 rather than
+        * 5,6,7, active aspect present in bit 6 rather than 4.
         */
-       val = (frame.colorspace & 3) << 4 | (frame.scan_mode & 0x3);
+       val = (frame.scan_mode & 3) << 4 | (frame.colorspace & 0x3);
        if (frame.active_aspect & 15)
                val |= HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT;
        if (frame.top_bar || frame.bottom_bar)
@@ -1332,17 +1374,23 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
 static void hdmi_av_composer(struct dw_hdmi *hdmi,
                             const struct drm_display_mode *mode)
 {
-       u8 inv_val;
+       u8 inv_val, bytes;
        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 hdisplay, vdisplay;
 
        vmode->mpixelclock = mode->crtc_clock * 1000;
-
+       if (mode->flags & DRM_MODE_FLAG_420_MASK)
+               vmode->mpixelclock /= 2;
        dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
 
-       /* Set up HDMI_FC_INVIDCONF */
-       inv_val = (hdmi->hdmi_data.hdcp_enable ?
+       /* Set up HDMI_FC_INVIDCONF
+        * fc_invidconf.HDCP_keepout must be set (1'b1)
+        * when activate the scrambler feature.
+        */
+       inv_val = (hdmi->hdmi_data.hdcp_enable ||
+                  vmode->mpixelclock > 340000000 ||
+                  hdmi->connector.lte_340mcsc_scramble ?
                HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
                HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
 
@@ -1375,6 +1423,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 (mode->flags & DRM_MODE_FLAG_420_MASK) {
+               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;
@@ -1391,16 +1455,35 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
                vsync_len /= 2;
        }
 
+       /* Scrambling Control */
+       if (hdmi->connector.scdc_present) {
+               if (vmode->mpixelclock > 340000000 ||
+                   hdmi->connector.lte_340mcsc_scramble) {
+                       drm_scdc_readb(&hdmi->i2c->adap, SCDC_SINK_VERSION,
+                                      &bytes);
+                       drm_scdc_writeb(&hdmi->i2c->adap, SCDC_SOURCE_VERSION,
+                                       bytes);
+                       drm_scdc_writeb(&hdmi->i2c->adap, SCDC_TMDS_CONFIG, 1);
+                       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_writeb(&hdmi->i2c->adap, SCDC_TMDS_CONFIG, 0);
+               }
+       }
+
        /* 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);
 
@@ -1408,7 +1491,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);
 
@@ -1416,7 +1498,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);
 
@@ -1537,10 +1618,13 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
                hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0;
        }
        /* TODO: Get input format from IPU (via FB driver interface) */
-       hdmi->hdmi_data.enc_in_format = RGB;
-
-       hdmi->hdmi_data.enc_out_format = RGB;
-
+       if (mode->flags & DRM_MODE_FLAG_420_MASK) {
+               hdmi->hdmi_data.enc_in_format = YCBCR420;
+               hdmi->hdmi_data.enc_out_format = YCBCR420;
+       } else {
+               hdmi->hdmi_data.enc_in_format = RGB;
+               hdmi->hdmi_data.enc_out_format = RGB;
+       }
        hdmi->hdmi_data.enc_color_depth = 8;
        /*
         * According to the dw-hdmi specification 6.4.2
@@ -2039,6 +2123,7 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
                return -ENOMEM;
 
        hdmi->connector.interlace_allowed = 1;
+       hdmi->connector.stereo_allowed = 1;
 
        hdmi->plat_data = plat_data;
        hdmi->dev = dev;
@@ -2048,6 +2133,7 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        hdmi->disabled = true;
        hdmi->rxsense = true;
        hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
+       hdmi->irq = irq;
 
        mutex_init(&hdmi->mutex);
        mutex_init(&hdmi->audio_mutex);
@@ -2247,6 +2333,49 @@ void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
 
+static void dw_hdmi_reg_initial(struct dw_hdmi *hdmi)
+{
+       if (hdmi_readb(hdmi, HDMI_IH_MUTE)) {
+               initialize_hdmi_ih_mutes(hdmi);
+               hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
+                           HDMI_PHY_I2CM_INT_ADDR);
+
+               hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
+                           HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
+                           HDMI_PHY_I2CM_CTLINT_ADDR);
+
+               hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE,
+                           HDMI_PHY_POL0);
+               hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
+               hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD |
+                           HDMI_IH_PHY_STAT0_RX_SENSE),
+                           HDMI_IH_MUTE_PHY_STAT0);
+       }
+}
+
+void dw_hdmi_suspend(struct device *dev)
+{
+       struct dw_hdmi *hdmi = dev_get_drvdata(dev);
+
+       mutex_lock(&hdmi->mutex);
+       if (hdmi->irq)
+               disable_irq(hdmi->irq);
+       mutex_unlock(&hdmi->mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_suspend);
+
+void dw_hdmi_resume(struct device *dev)
+{
+       struct dw_hdmi *hdmi = dev_get_drvdata(dev);
+
+       mutex_lock(&hdmi->mutex);
+       dw_hdmi_reg_initial(hdmi);
+       if (hdmi->irq)
+               enable_irq(hdmi->irq);
+       mutex_unlock(&hdmi->mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_resume);
+
 MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
 MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
 MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");