drm/bridge: dw_hdmi: set vdisplay for frame packing 3d mode
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / bridge / dw-hdmi.c
index 183c2ab521a657131d7701a7910234751e30586f..d05e1095a0ec5673b86f1fba66b45d8b55226869 100644 (file)
 #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,
@@ -173,6 +175,10 @@ struct dw_hdmi_i2c {
 
        u8                      slave_reg;
        bool                    is_regaddr;
+       bool                    is_segment;
+
+       unsigned int            scl_high_ns;
+       unsigned int            scl_low_ns;
 };
 
 struct dw_hdmi {
@@ -220,6 +226,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);
@@ -277,6 +284,49 @@ static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg,
        hdmi_modb(hdmi, data << shift, mask, reg);
 }
 
+static void dw_hdmi_i2c_set_divs(struct dw_hdmi *hdmi)
+{
+       unsigned long clk_rate_khz;
+       unsigned long low_ns, high_ns;
+       unsigned long div_low, div_high;
+
+       /* Standard-mode */
+       if (hdmi->i2c->scl_high_ns < 4000)
+               high_ns = 4708;
+       else
+               high_ns = hdmi->i2c->scl_high_ns;
+
+       if (hdmi->i2c->scl_low_ns < 4700)
+               low_ns = 4916;
+       else
+               low_ns = hdmi->i2c->scl_low_ns;
+
+       /* Adjust to avoid overflow */
+       clk_rate_khz = DIV_ROUND_UP(clk_get_rate(hdmi->isfr_clk), 1000);
+
+       div_low = (clk_rate_khz * low_ns) / 1000000;
+       if ((clk_rate_khz * low_ns) % 1000000)
+               div_low++;
+
+       div_high = (clk_rate_khz * high_ns) / 1000000;
+       if ((clk_rate_khz * high_ns) % 1000000)
+               div_high++;
+
+       /* Maximum divider supported by hw is 0xffff */
+       if (div_low > 0xffff)
+               div_low = 0xffff;
+
+       if (div_high > 0xffff)
+               div_high = 0xffff;
+
+       hdmi_writeb(hdmi, div_high & 0xff, HDMI_I2CM_SS_SCL_HCNT_0_ADDR);
+       hdmi_writeb(hdmi, (div_high >> 8) & 0xff,
+                   HDMI_I2CM_SS_SCL_HCNT_1_ADDR);
+       hdmi_writeb(hdmi, div_low & 0xff, HDMI_I2CM_SS_SCL_LCNT_0_ADDR);
+       hdmi_writeb(hdmi, (div_low >> 8) & 0xff,
+                   HDMI_I2CM_SS_SCL_LCNT_1_ADDR);
+}
+
 static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
 {
        /* Software reset */
@@ -298,6 +348,8 @@ static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
        /* Mute DONE and ERROR interrupts */
        hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
                    HDMI_IH_MUTE_I2CM_STAT0);
+
+       dw_hdmi_i2c_set_divs(hdmi);
 }
 
 static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
@@ -316,8 +368,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)
@@ -329,6 +385,7 @@ static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
 
                *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
        }
+       i2c->is_segment = false;
 
        return 0;
 }
@@ -378,12 +435,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",
@@ -402,15 +453,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;
        }
@@ -706,7 +766,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)
@@ -857,7 +918,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) {
@@ -1013,6 +1075,23 @@ static int hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
        return 0;
 }
 
+static int hdmi_phy_i2c_read(struct dw_hdmi *hdmi, unsigned char addr)
+{
+       int val;
+
+       hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0);
+       hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
+       hdmi_writeb(hdmi, 0, HDMI_PHY_I2CM_DATAI_1_ADDR);
+       hdmi_writeb(hdmi, 0, HDMI_PHY_I2CM_DATAI_0_ADDR);
+       hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_READ,
+                   HDMI_PHY_I2CM_OPERATION_ADDR);
+       hdmi_phy_wait_i2c_done(hdmi, 1000);
+       val = hdmi_readb(hdmi, HDMI_PHY_I2CM_DATAI_1_ADDR);
+       val = (val & 0xff) << 8;
+       val += hdmi_readb(hdmi, HDMI_PHY_I2CM_DATAI_0_ADDR) & 0xff;
+       return val;
+}
+
 static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable)
 {
        hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0,
@@ -1148,8 +1227,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 */
@@ -1251,14 +1339,20 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
 {
        struct hdmi_avi_infoframe frame;
        u8 val;
+       bool is_hdmi2 = false;
 
+       if ((mode->flags & DRM_MODE_FLAG_420_MASK) ||
+           hdmi->connector.scdc_present)
+               is_hdmi2 = true;
        /* Initialise info frame from DRM mode */
-       drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+       drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, is_hdmi2);
 
        if (hdmi->hdmi_data.enc_out_format == YCBCR444)
                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;
 
@@ -1346,16 +1440,74 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
        hdmi_writeb(hdmi, (frame.right_bar >> 8) & 0xff, HDMI_FC_AVISRB1);
 }
 
+static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi,
+                                                struct drm_display_mode *mode)
+{
+       struct hdmi_vendor_infoframe frame;
+       u8 buffer[10];
+       ssize_t err;
+
+       /* Disable HDMI vendor specific infoframe send */
+       hdmi_mask_writeb(hdmi, 0, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET,
+                       HDMI_FC_DATAUTO0_VSD_MASK);
+
+       err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mode);
+       if (err < 0)
+               /*
+                * Going into that statement does not means vendor infoframe
+                * fails. It just informed us that vendor infoframe is not
+                * needed for the selected mode. Only 4k or stereoscopic 3D
+                * mode requires vendor infoframe. So just simply return.
+                */
+               return;
+
+       err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer));
+       if (err < 0) {
+               dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n",
+                       err);
+               return;
+       }
+
+       /* Set the length of HDMI vendor specific InfoFrame payload */
+       hdmi_writeb(hdmi, buffer[2], HDMI_FC_VSDSIZE);
+
+       /* Set 24bit IEEE Registration Identifier */
+       hdmi_writeb(hdmi, buffer[4], HDMI_FC_VSDIEEEID0);
+       hdmi_writeb(hdmi, buffer[5], HDMI_FC_VSDIEEEID1);
+       hdmi_writeb(hdmi, buffer[6], HDMI_FC_VSDIEEEID2);
+
+       /* Set HDMI_Video_Format and HDMI_VIC/3D_Structure */
+       hdmi_writeb(hdmi, buffer[7], HDMI_FC_VSDPAYLOAD0);
+       hdmi_writeb(hdmi, buffer[8], HDMI_FC_VSDPAYLOAD1);
+
+       if (frame.s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF)
+               hdmi_writeb(hdmi, buffer[9], HDMI_FC_VSDPAYLOAD2);
+
+       /* Packet frame interpolation */
+       hdmi_writeb(hdmi, 1, HDMI_FC_DATAUTO1);
+
+       /* Auto packets per frame and line spacing */
+       hdmi_writeb(hdmi, 0x11, HDMI_FC_DATAUTO2);
+
+       /* Configures the Frame Composer On RDRB mode */
+       hdmi_mask_writeb(hdmi, 1, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET,
+                       HDMI_FC_DATAUTO0_VSD_MASK);
+}
+
 static void hdmi_av_composer(struct dw_hdmi *hdmi,
                             const struct drm_display_mode *mode)
 {
        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;
+       if ((mode->flags & DRM_MODE_FLAG_3D_MASK) ==
+               DRM_MODE_FLAG_3D_FRAME_PACKING)
+               vmode->mpixelclock *= 2;
        dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
 
        /* Set up HDMI_FC_INVIDCONF
@@ -1397,6 +1549,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;
@@ -1411,6 +1579,9 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
                vblank /= 2;
                v_de_vs /= 2;
                vsync_len /= 2;
+       } else if ((mode->flags & DRM_MODE_FLAG_3D_MASK) ==
+               DRM_MODE_FLAG_3D_FRAME_PACKING) {
+               vdisplay += mode->vtotal;
        }
 
        /* Scrambling Control */
@@ -1434,15 +1605,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);
 
@@ -1450,7 +1620,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);
 
@@ -1458,7 +1627,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);
 
@@ -1579,10 +1747,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
@@ -1620,6 +1791,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
 
                /* HDMI Initialization Step F - Configure AVI InfoFrame */
                hdmi_config_AVI(hdmi, mode);
+               hdmi_config_vendor_specific_infoframe(hdmi, mode);
        } else {
                dev_dbg(hdmi->dev, "%s DVI mode\n", __func__);
        }
@@ -2061,6 +2233,158 @@ static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi)
        return 0;
 }
 
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+struct dw_hdmi_reg_table {
+       int reg_base;
+       int reg_end;
+};
+
+static const struct dw_hdmi_reg_table hdmi_reg_table[] = {
+       {HDMI_DESIGN_ID, HDMI_CONFIG3_ID},
+       {HDMI_IH_FC_STAT0, HDMI_IH_MUTE},
+       {HDMI_TX_INVID0, HDMI_TX_BCBDATA1},
+       {HDMI_VP_STATUS, HDMI_VP_POL},
+       {HDMI_FC_INVIDCONF, HDMI_FC_DBGTMDS2},
+       {HDMI_PHY_CONF0, HDMI_PHY_POL0},
+       {HDMI_PHY_I2CM_SLAVE_ADDR, HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR},
+       {HDMI_AUD_CONF0, 0x3624},
+       {HDMI_MC_SFRDIV, HDMI_MC_HEACPHY_RST},
+       {HDMI_CSC_CFG, HDMI_CSC_COEF_C4_LSB},
+       {HDMI_A_HDCPCFG0, 0x52bb},
+       {0x7800, 0x7818},
+       {0x7900, 0x790e},
+       {HDMI_CEC_CTRL, HDMI_CEC_WKUPCTRL},
+       {HDMI_I2CM_SLAVE, 0x7e31},
+};
+
+static int dw_hdmi_ctrl_show(struct seq_file *s, void *v)
+{
+       struct dw_hdmi *hdmi = s->private;
+       u32 i = 0, j = 0, val = 0;
+
+       seq_puts(s, "\n>>>hdmi_ctl reg ");
+       for (i = 0; i < 16; i++)
+               seq_printf(s, " %2x", i);
+       seq_puts(s, "\n---------------------------------------------------");
+
+       for (i = 0; i < ARRAY_SIZE(hdmi_reg_table); i++) {
+               for (j = hdmi_reg_table[i].reg_base;
+                    j <= hdmi_reg_table[i].reg_end; j++) {
+                       val = hdmi_readb(hdmi, j);
+                       if ((j - hdmi_reg_table[i].reg_base) % 16 == 0)
+                               seq_printf(s, "\n>>>hdmi_ctl %04x:", j);
+                       seq_printf(s, " %02x", val);
+               }
+       }
+       seq_puts(s, "\n---------------------------------------------------\n");
+
+       return 0;
+}
+
+static int dw_hdmi_ctrl_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, dw_hdmi_ctrl_show, inode->i_private);
+}
+
+static ssize_t
+dw_hdmi_ctrl_write(struct file *file, const char __user *buf,
+                  size_t count, loff_t *ppos)
+{
+       struct dw_hdmi *hdmi =
+               ((struct seq_file *)file->private_data)->private;
+       u32 reg, val;
+       char kbuf[25];
+
+       if (copy_from_user(kbuf, buf, count))
+               return -EFAULT;
+       if (sscanf(kbuf, "%x%x", &reg, &val) == -1)
+               return -EFAULT;
+       if ((reg < 0) || (reg > HDMI_I2CM_FS_SCL_LCNT_0_ADDR)) {
+               dev_err(hdmi->dev, "it is no a hdmi register\n");
+               return count;
+       }
+       dev_info(hdmi->dev, "/**********hdmi register config******/");
+       dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val);
+       hdmi_writeb(hdmi, val, reg);
+       return count;
+}
+
+static const struct file_operations dw_hdmi_ctrl_fops = {
+       .owner = THIS_MODULE,
+       .open = dw_hdmi_ctrl_open,
+       .read = seq_read,
+       .write = dw_hdmi_ctrl_write,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static int dw_hdmi_phy_show(struct seq_file *s, void *v)
+{
+       struct dw_hdmi *hdmi = s->private;
+       u32 i;
+
+       seq_puts(s, "\n>>>hdmi_phy reg ");
+       for (i = 0; i < 0x28; i++)
+               seq_printf(s, "regs %02x val %04x\n",
+                          i, hdmi_phy_i2c_read(hdmi, i));
+       return 0;
+}
+
+static int dw_hdmi_phy_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, dw_hdmi_phy_show, inode->i_private);
+}
+
+static ssize_t
+dw_hdmi_phy_write(struct file *file, const char __user *buf,
+                 size_t count, loff_t *ppos)
+{
+       struct dw_hdmi *hdmi =
+               ((struct seq_file *)file->private_data)->private;
+       u32 reg, val;
+       char kbuf[25];
+
+       if (copy_from_user(kbuf, buf, count))
+               return -EFAULT;
+       if (sscanf(kbuf, "%x%x", &reg, &val) == -1)
+               return -EFAULT;
+       if ((reg < 0) || (reg > 0x28)) {
+               dev_err(hdmi->dev, "it is not a hdmi phy register\n");
+               return count;
+       }
+       dev_info(hdmi->dev, "/*******hdmi phy register config******/");
+       dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val);
+       hdmi_phy_i2c_write(hdmi, val, reg);
+       return count;
+}
+
+static const struct file_operations dw_hdmi_phy_fops = {
+       .owner = THIS_MODULE,
+       .open = dw_hdmi_phy_open,
+       .read = seq_read,
+       .write = dw_hdmi_phy_write,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static void dw_hdmi_register_debugfs(struct device *dev, struct dw_hdmi *hdmi)
+{
+       struct dentry *debugfs_dir;
+
+       debugfs_dir = debugfs_create_dir("dw-hdmi", NULL);
+       if (IS_ERR(debugfs_dir)) {
+               dev_err(dev, "failed to create debugfs dir!\n");
+               return;
+       }
+       debugfs_create_file("ctrl", 0400, debugfs_dir,
+                           hdmi, &dw_hdmi_ctrl_fops);
+       debugfs_create_file("phy", 0400, debugfs_dir,
+                           hdmi, &dw_hdmi_phy_fops);
+}
+
 int dw_hdmi_bind(struct device *dev, struct device *master,
                 void *data, struct drm_encoder *encoder,
                 struct resource *iores, int irq,
@@ -2081,6 +2405,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;
@@ -2090,6 +2415,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);
@@ -2129,6 +2455,16 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
                hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
                if (IS_ERR(hdmi->ddc))
                        hdmi->ddc = NULL;
+               /*
+                * Read high and low time from device tree. If not available use
+                * the default timing scl clock rate is about 99.6KHz.
+                */
+               if (of_property_read_u32(np, "ddc-i2c-scl-high-time-ns",
+                                        &hdmi->i2c->scl_high_ns))
+                       hdmi->i2c->scl_high_ns = 4708;
+               if (of_property_read_u32(np, "ddc-i2c-scl-low-time-ns",
+                                        &hdmi->i2c->scl_low_ns))
+                       hdmi->i2c->scl_low_ns = 4916;
        }
 
        hdmi->regs = devm_ioremap_resource(dev, iores);
@@ -2249,6 +2585,8 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
 
        dev_set_drvdata(dev, hdmi);
 
+       dw_hdmi_register_debugfs(dev, hdmi);
+
        return 0;
 
 err_iahb:
@@ -2289,6 +2627,51 @@ 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->i2c)
+               dw_hdmi_i2c_init(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>");