FROMLIST: drm: bridge: dw-hdmi: add HDMI vendor specific infoframe config
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / bridge / dw-hdmi.c
index b0aac4733020e337b5cc0e99a51ac4ab67f15ba3..7aab06712118ac49958b766d7e9882409ba95aaa 100644 (file)
@@ -1,5 +1,9 @@
 /*
+ * DesignWare High-Definition Multimedia Interface (HDMI) driver
+ *
+ * Copyright (C) 2013-2015 Mentor Graphics Inc.
  * Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -8,7 +12,6 @@
  *
  * Designware High-Definition Multimedia Interface (HDMI) driver
  *
- * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
  */
 #include <linux/module.h>
 #include <linux/irq.h>
 #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>
+#endif
 
 #include "dw-hdmi.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,
@@ -53,6 +62,62 @@ enum hdmi_datamap {
        YCbCr422_12B = 0x12,
 };
 
+/*
+ * Unless otherwise noted, entries in this table are 100% optimization.
+ * Values can be obtained from hdmi_compute_n() but that function is
+ * slow so we pre-compute values we expect to see.
+ *
+ * All 32k and 48k values are expected to be the same (due to the way
+ * the math works) for any rate that's an exact kHz.
+ */
+static const struct dw_hdmi_audio_tmds_n common_tmds_n_table[] = {
+       { .tmds = 25175000, .n_32k = 4096, .n_44k1 = 12854, .n_48k = 6144, },
+       { .tmds = 25200000, .n_32k = 4096, .n_44k1 = 5656, .n_48k = 6144, },
+       { .tmds = 27000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
+       { .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, },
+       { .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, },
+       { .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
+       { .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
+       { .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
+       { .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
+       { .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
+       { .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
+       { .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
+       { .tmds = 54000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
+       { .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
+       { .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, },
+       { .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
+       { .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, },
+       { .tmds = 73250000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
+       { .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
+       { .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, },
+       { .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
+       { .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
+       { .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
+       { .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
+       { .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
+       { .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
+       { .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
+       { .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
+       { .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
+       { .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
+       { .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, },
+       { .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
+       { .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
+       { .tmds = 146250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
+       { .tmds = 148500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
+       { .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
+       { .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
+
+       /* For 297 MHz+ HDMI spec have some other rule for setting N */
+       { .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, },
+       { .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240, },
+
+       /* End of table */
+       { .tmds = 0,         .n_32k = 0,    .n_44k1 = 0,    .n_48k = 0, },
+};
+
+
 static const u16 csc_coeff_default[3][4] = {
        { 0x2000, 0x0000, 0x0000, 0x0000 },
        { 0x0000, 0x2000, 0x0000, 0x0000 },
@@ -101,6 +166,18 @@ struct hdmi_data_info {
        struct hdmi_vmode video_mode;
 };
 
+struct dw_hdmi_i2c {
+       struct i2c_adapter      adap;
+
+       struct mutex            lock;
+       struct completion       cmp;
+       u8                      stat;
+
+       u8                      slave_reg;
+       bool                    is_regaddr;
+       bool                    is_segment;
+};
+
 struct dw_hdmi {
        struct drm_connector connector;
        struct drm_encoder *encoder;
@@ -111,6 +188,7 @@ struct dw_hdmi {
        struct device *dev;
        struct clk *isfr_clk;
        struct clk *iahb_clk;
+       struct dw_hdmi_i2c *i2c;
 
        struct hdmi_data_info hdmi_data;
        const struct dw_hdmi_plat_data *plat_data;
@@ -142,6 +220,11 @@ struct dw_hdmi {
        unsigned int audio_n;
        bool audio_enable;
 
+#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);
 };
@@ -198,6 +281,210 @@ 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_init(struct dw_hdmi *hdmi)
+{
+       /* Software reset */
+       hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
+
+       /* Set Standard Mode speed */
+       hdmi_modb(hdmi, HDMI_I2CM_DIV_STD_MODE,
+                 HDMI_I2CM_DIV_FAST_STD_MODE, HDMI_I2CM_DIV);
+
+       /* Set done, not acknowledged and arbitration interrupt polarities */
+       hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT);
+       hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL,
+                   HDMI_I2CM_CTLINT);
+
+       /* Clear DONE and ERROR interrupts */
+       hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+                   HDMI_IH_I2CM_STAT0);
+
+       /* Mute DONE and ERROR interrupts */
+       hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+                   HDMI_IH_MUTE_I2CM_STAT0);
+}
+
+static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
+                           unsigned char *buf, unsigned int length)
+{
+       struct dw_hdmi_i2c *i2c = hdmi->i2c;
+       int stat;
+
+       if (!i2c->is_regaddr) {
+               dev_dbg(hdmi->dev, "set read register address to 0\n");
+               i2c->slave_reg = 0x00;
+               i2c->is_regaddr = true;
+       }
+
+       while (length--) {
+               reinit_completion(&i2c->cmp);
+
+               hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+               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)
+                       return -EAGAIN;
+
+               /* Check for error condition on the bus */
+               if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+                       return -EIO;
+
+               *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
+       }
+       i2c->is_segment = false;
+
+       return 0;
+}
+
+static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
+                            unsigned char *buf, unsigned int length)
+{
+       struct dw_hdmi_i2c *i2c = hdmi->i2c;
+       int stat;
+
+       if (!i2c->is_regaddr) {
+               /* Use the first write byte as register address */
+               i2c->slave_reg = buf[0];
+               length--;
+               buf++;
+               i2c->is_regaddr = true;
+       }
+
+       while (length--) {
+               reinit_completion(&i2c->cmp);
+
+               hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO);
+               hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+               hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE,
+                           HDMI_I2CM_OPERATION);
+
+       stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+               if (!stat)
+                       return -EAGAIN;
+
+               /* Check for error condition on the bus */
+               if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
+                           struct i2c_msg *msgs, int num)
+{
+       struct dw_hdmi *hdmi = i2c_get_adapdata(adap);
+       struct dw_hdmi_i2c *i2c = hdmi->i2c;
+       u8 addr = msgs[0].addr;
+       int i, ret = 0;
+
+       dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr);
+
+       for (i = 0; i < num; i++) {
+               if (msgs[i].len == 0) {
+                       dev_dbg(hdmi->dev,
+                               "unsupported transfer %d/%d, no data\n",
+                               i + 1, num);
+                       return -EOPNOTSUPP;
+               }
+       }
+
+       mutex_lock(&i2c->lock);
+
+       hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0);
+
+       /* Set slave device address taken from the first I2C message */
+       hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE);
+
+       /* 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].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;
+       }
+
+       if (!ret)
+               ret = num;
+
+       /* Mute DONE and ERROR interrupts */
+       hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+                   HDMI_IH_MUTE_I2CM_STAT0);
+
+       mutex_unlock(&i2c->lock);
+
+       return ret;
+}
+
+static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm dw_hdmi_algorithm = {
+       .master_xfer    = dw_hdmi_i2c_xfer,
+       .functionality  = dw_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
+{
+       struct i2c_adapter *adap;
+       struct dw_hdmi_i2c *i2c;
+       int ret;
+
+       i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+       if (!i2c)
+               return ERR_PTR(-ENOMEM);
+
+       mutex_init(&i2c->lock);
+       init_completion(&i2c->cmp);
+
+       adap = &i2c->adap;
+       adap->class = I2C_CLASS_DDC;
+       adap->owner = THIS_MODULE;
+       adap->dev.parent = hdmi->dev;
+       adap->dev.of_node = hdmi->dev->of_node;
+       adap->algo = &dw_hdmi_algorithm;
+       strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name));
+       i2c_set_adapdata(adap, hdmi);
+
+       ret = i2c_add_adapter(adap);
+       if (ret) {
+               dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
+               devm_kfree(hdmi->dev, i2c);
+               return ERR_PTR(ret);
+       }
+
+       hdmi->i2c = i2c;
+
+       dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+       return adap;
+}
+
 static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts,
                           unsigned int n)
 {
@@ -217,60 +504,117 @@ static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts,
        hdmi_writeb(hdmi, n & 0xff, HDMI_AUD_N1);
 }
 
-static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
+static int hdmi_match_tmds_n_table(struct dw_hdmi *hdmi,
+                                  unsigned long pixel_clk,
+                                  unsigned long freq)
 {
-       unsigned int n = (128 * freq) / 1000;
-       unsigned int mult = 1;
+       const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data;
+       const struct dw_hdmi_audio_tmds_n *tmds_n = NULL;
+       int i;
+
+       if (plat_data->tmds_n_table) {
+               for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) {
+                       if (pixel_clk == plat_data->tmds_n_table[i].tmds) {
+                               tmds_n = &plat_data->tmds_n_table[i];
+                               break;
+                       }
+               }
+       }
 
-       while (freq > 48000) {
-               mult *= 2;
-               freq /= 2;
+       if (tmds_n == NULL) {
+               for (i = 0; common_tmds_n_table[i].tmds != 0; i++) {
+                       if (pixel_clk == common_tmds_n_table[i].tmds) {
+                               tmds_n = &common_tmds_n_table[i];
+                               break;
+                       }
+               }
        }
 
+       if (tmds_n == NULL)
+               return -ENOENT;
+
        switch (freq) {
        case 32000:
-               if (pixel_clk == 25175000)
-                       n = 4576;
-               else if (pixel_clk == 27027000)
-                       n = 4096;
-               else if (pixel_clk == 74176000 || pixel_clk == 148352000)
-                       n = 11648;
-               else
-                       n = 4096;
-               n *= mult;
-               break;
-
+               return tmds_n->n_32k;
        case 44100:
-               if (pixel_clk == 25175000)
-                       n = 7007;
-               else if (pixel_clk == 74176000)
-                       n = 17836;
-               else if (pixel_clk == 148352000)
-                       n = 8918;
-               else
-                       n = 6272;
-               n *= mult;
-               break;
-
+       case 88200:
+       case 176400:
+               return (freq / 44100) * tmds_n->n_44k1;
        case 48000:
-               if (pixel_clk == 25175000)
-                       n = 6864;
-               else if (pixel_clk == 27027000)
-                       n = 6144;
-               else if (pixel_clk == 74176000)
-                       n = 11648;
-               else if (pixel_clk == 148352000)
-                       n = 5824;
-               else
-                       n = 6144;
-               n *= mult;
-               break;
-
+       case 96000:
+       case 192000:
+               return (freq / 48000) * tmds_n->n_48k;
        default:
-               break;
+               return -ENOENT;
        }
+}
+
+static u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n,
+                               unsigned int pixel_clk)
+{
+       u64 final, diff;
+       u64 cts;
+
+       final = (u64)pixel_clk * n;
+
+       cts = final;
+       do_div(cts, 128 * freq);
 
-       return n;
+       diff = final - (u64)cts * (128 * freq);
+
+       return diff;
+}
+
+static unsigned int hdmi_compute_n(struct dw_hdmi *hdmi,
+                                  unsigned long pixel_clk,
+                                  unsigned long freq)
+{
+       unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500);
+       unsigned int max_n = (128 * freq) / 300;
+       unsigned int ideal_n = (128 * freq) / 1000;
+       unsigned int best_n_distance = ideal_n;
+       unsigned int best_n = 0;
+       u64 best_diff = U64_MAX;
+       int n;
+
+       /* If the ideal N could satisfy the audio math, then just take it */
+       if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0)
+               return ideal_n;
+
+       for (n = min_n; n <= max_n; n++) {
+               u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk);
+
+               if (diff < best_diff || (diff == best_diff &&
+                   abs(n - ideal_n) < best_n_distance)) {
+                       best_n = n;
+                       best_diff = diff;
+                       best_n_distance = abs(best_n - ideal_n);
+               }
+
+               /*
+                * The best N already satisfy the audio math, and also be
+                * the closest value to ideal N, so just cut the loop.
+                */
+               if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance))
+                       break;
+       }
+
+       return best_n;
+}
+
+static unsigned int hdmi_find_n(struct dw_hdmi *hdmi, unsigned long pixel_clk,
+                               unsigned long sample_rate)
+{
+       int n;
+
+       n = hdmi_match_tmds_n_table(hdmi, pixel_clk, sample_rate);
+       if (n > 0)
+               return n;
+
+       dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n",
+                pixel_clk);
+
+       return hdmi_compute_n(hdmi, pixel_clk, sample_rate);
 }
 
 static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
@@ -280,7 +624,7 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
        unsigned int n, cts;
        u64 tmp;
 
-       n = hdmi_compute_n(sample_rate, pixel_clk);
+       n = hdmi_find_n(hdmi, pixel_clk, sample_rate);
 
        /*
         * Compute the CTS value from the N value.  Note that CTS and N
@@ -374,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)
@@ -525,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) {
@@ -567,7 +913,7 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
                  HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF);
 
        /* Data from pixel repeater block */
-       if (hdmi_data->pix_repet_factor > 1) {
+       if (hdmi_data->pix_repet_factor > 0) {
                vp_conf = HDMI_VP_CONF_PR_EN_ENABLE |
                          HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER;
        } else { /* data from packetizer block */
@@ -734,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;
@@ -796,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);
@@ -806,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 */
@@ -825,18 +1190,24 @@ 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 */
        dw_hdmi_phy_gen2_txpwron(hdmi, 1);
        dw_hdmi_phy_gen2_pddq(hdmi, 0);
 
-       if (hdmi->dev_type == RK3288_HDMI)
+       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;
@@ -911,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;
 
@@ -940,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)
@@ -998,20 +1371,80 @@ 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;
+       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;
-
-       vmode->mpixelclock = mode->clock * 1000;
+       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);
 
@@ -1044,6 +1477,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;
@@ -1060,16 +1509,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);
 
@@ -1077,7 +1545,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);
 
@@ -1085,7 +1552,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);
 
@@ -1132,6 +1598,12 @@ static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi)
                clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE;
                hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS);
        }
+
+       /* Enable pixel repetition path */
+       if (hdmi->hdmi_data.video_mode.mpixelrepetitioninput) {
+               clkdis &= ~HDMI_MC_CLKDIS_PREPCLK_DISABLE;
+               hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS);
+       }
 }
 
 static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi)
@@ -1192,16 +1664,30 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
        else
                hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
 
-       hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
-       hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0;
-
+       if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
+               hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1;
+               hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 1;
+       } else {
+               hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
+               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;
-       hdmi->hdmi_data.pix_repet_factor = 0;
+       /*
+        * According to the dw-hdmi specification 6.4.2
+        * vp_pr_cd[3:0]:
+        * 0000b: No pixel repetition (pixel sent only once)
+        * 0001b: Pixel sent two times (pixel repeated once)
+        */
+       hdmi->hdmi_data.pix_repet_factor =
+               (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 1 : 0;
        hdmi->hdmi_data.hdcp_enable = 0;
        hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
 
@@ -1230,6 +1716,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__);
        }
@@ -1246,26 +1733,6 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
        return 0;
 }
 
-/* Wait until we are registered to enable interrupts */
-static int dw_hdmi_fb_registered(struct dw_hdmi *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);
-
-       /* enable cable hot plug irq */
-       hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
-
-       /* Clear Hotplug interrupts */
-       hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE,
-                   HDMI_IH_PHY_STAT0);
-
-       return 0;
-}
-
 static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi)
 {
        u8 ih_mute;
@@ -1391,13 +1858,6 @@ static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
        mutex_unlock(&hdmi->mutex);
 }
 
-static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
-                                     const struct drm_display_mode *mode,
-                                     struct drm_display_mode *adjusted_mode)
-{
-       return true;
-}
-
 static void dw_hdmi_bridge_disable(struct drm_bridge *bridge)
 {
        struct dw_hdmi *hdmi = bridge->driver_private;
@@ -1478,10 +1938,6 @@ dw_hdmi_connector_mode_valid(struct drm_connector *connector,
                                           struct dw_hdmi, connector);
        enum drm_mode_status mode_status = MODE_OK;
 
-       /* We don't support double-clocked modes */
-       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
-               return MODE_BAD;
-
        if (hdmi->plat_data->mode_valid)
                mode_status = hdmi->plat_data->mode_valid(connector, mode);
 
@@ -1546,19 +2002,42 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
        .pre_enable = dw_hdmi_bridge_nop,
        .post_disable = dw_hdmi_bridge_nop,
        .mode_set = dw_hdmi_bridge_mode_set,
-       .mode_fixup = dw_hdmi_bridge_mode_fixup,
 };
 
+static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi)
+{
+       struct dw_hdmi_i2c *i2c = hdmi->i2c;
+       unsigned int stat;
+
+       stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0);
+       if (!stat)
+               return IRQ_NONE;
+
+       hdmi_writeb(hdmi, stat, HDMI_IH_I2CM_STAT0);
+
+       i2c->stat = stat;
+
+       complete(&i2c->cmp);
+
+       return IRQ_HANDLED;
+}
+
 static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
 {
        struct dw_hdmi *hdmi = dev_id;
        u8 intr_stat;
+       irqreturn_t ret = IRQ_NONE;
+
+       if (hdmi->i2c)
+               ret = dw_hdmi_i2c_irq(hdmi);
 
        intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
-       if (intr_stat)
+       if (intr_stat) {
                hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+               return IRQ_WAKE_THREAD;
+       }
 
-       return intr_stat ? IRQ_WAKE_THREAD : IRQ_NONE;
+       return ret;
 }
 
 static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
@@ -1595,7 +2074,7 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
        if (intr_stat &
            (HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) {
                mutex_lock(&hdmi->mutex);
-               if (!hdmi->disabled && !hdmi->force) {
+               if (!hdmi->bridge_is_on && !hdmi->force) {
                        /*
                         * If the RX sense status indicates we're disconnected,
                         * clear the software rxsense status.
@@ -1622,6 +2101,12 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
                dev_dbg(hdmi->dev, "EVENT=%s\n",
                        phy_int_pol & HDMI_PHY_HPD ? "plugin" : "plugout");
                drm_helper_hpd_irq_event(hdmi->bridge->dev);
+#ifdef CONFIG_SWITCH
+               if (phy_int_pol & HDMI_PHY_HPD)
+                       switch_set_state(&hdmi->switchdev, 1);
+               else
+                       switch_set_state(&hdmi->switchdev, 0);
+#endif
        }
 
        hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
@@ -1654,6 +2139,7 @@ static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi)
 
        encoder->bridge = bridge;
        hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+       hdmi->connector.port = hdmi->dev->of_node;
 
        drm_connector_helper_add(&hdmi->connector,
                                 &dw_hdmi_connector_helper_funcs);
@@ -1681,16 +2167,18 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        struct device_node *np = dev->of_node;
        struct platform_device_info pdevinfo;
        struct device_node *ddc_node;
-       struct dw_hdmi_audio_data audio;
        struct dw_hdmi *hdmi;
        int ret;
        u32 val = 1;
+       u8 config0;
+       u8 config1;
 
        hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
        if (!hdmi)
                return -ENOMEM;
 
        hdmi->connector.interlace_allowed = 1;
+       hdmi->connector.stereo_allowed = 1;
 
        hdmi->plat_data = plat_data;
        hdmi->dev = dev;
@@ -1700,6 +2188,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);
@@ -1734,6 +2223,13 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
                dev_dbg(hdmi->dev, "no ddc property found\n");
        }
 
+       /* If DDC bus is not specified, try to register HDMI I2C bus */
+       if (!hdmi->ddc) {
+               hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
+               if (IS_ERR(hdmi->ddc))
+                       hdmi->ddc = NULL;
+       }
+
        hdmi->regs = devm_ioremap_resource(dev, iores);
        if (IS_ERR(hdmi->regs))
                return PTR_ERR(hdmi->regs);
@@ -1786,33 +2282,45 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
         */
        hdmi_init_clk_regenerator(hdmi);
 
-       /*
-        * Configure registers related to HDMI interrupt
-        * generation before registering IRQ.
-        */
-       hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0);
+       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);
 
-       /* Clear Hotplug interrupts */
-       hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE,
-                   HDMI_IH_PHY_STAT0);
+       /* Re-init HPD polarity */
+       hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0);
 
-       ret = dw_hdmi_fb_registered(hdmi);
-       if (ret)
-               goto err_iahb;
+       /* Unmask HPD, clear transitory interrupts, then unmute */
+       hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
 
        ret = dw_hdmi_register(drm, hdmi);
        if (ret)
                goto err_iahb;
 
-       /* Unmute interrupts */
+#ifdef CONFIG_SWITCH
+       hdmi->switchdev.name = "hdmi";
+       switch_dev_register(&hdmi->switchdev);
+#endif
+
        hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
                    HDMI_IH_MUTE_PHY_STAT0);
 
+       /* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */
+       if (hdmi->i2c)
+               dw_hdmi_i2c_init(hdmi);
+
        memset(&pdevinfo, 0, sizeof(pdevinfo));
        pdevinfo.parent = dev;
        pdevinfo.id = PLATFORM_DEVID_AUTO;
 
-       if (hdmi_readb(hdmi, HDMI_CONFIG1_ID) & HDMI_CONFIG1_AHB) {
+       config0 = hdmi_readb(hdmi, HDMI_CONFIG0_ID);
+       config1 = hdmi_readb(hdmi, HDMI_CONFIG1_ID);
+
+       if (config1 & HDMI_CONFIG1_AHB) {
+               struct dw_hdmi_audio_data audio;
+
                audio.phys = iores->start;
                audio.base = hdmi->regs;
                audio.irq = irq;
@@ -1824,6 +2332,18 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
                pdevinfo.size_data = sizeof(audio);
                pdevinfo.dma_mask = DMA_BIT_MASK(32);
                hdmi->audio = platform_device_register_full(&pdevinfo);
+       } else if (config0 & HDMI_CONFIG0_I2S) {
+               struct dw_hdmi_i2s_audio_data audio;
+
+               audio.hdmi      = hdmi;
+               audio.write     = hdmi_writeb;
+               audio.read      = hdmi_readb;
+
+               pdevinfo.name = "dw-hdmi-i2s-audio";
+               pdevinfo.data = &audio;
+               pdevinfo.size_data = sizeof(audio);
+               pdevinfo.dma_mask = DMA_BIT_MASK(32);
+               hdmi->audio = platform_device_register_full(&pdevinfo);
        }
 
        dev_set_drvdata(dev, hdmi);
@@ -1831,6 +2351,9 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        return 0;
 
 err_iahb:
+       if (hdmi->i2c)
+               i2c_del_adapter(&hdmi->i2c->adap);
+
        clk_disable_unprepare(hdmi->iahb_clk);
 err_isfr:
        clk_disable_unprepare(hdmi->isfr_clk);
@@ -1849,18 +2372,69 @@ void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
        /* Disable all interrupts */
        hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
 
+#ifdef CONFIG_SWITCH
+       switch_dev_unregister(&hdmi->switchdev);
+#endif
        hdmi->connector.funcs->destroy(&hdmi->connector);
        hdmi->encoder->funcs->destroy(hdmi->encoder);
 
        clk_disable_unprepare(hdmi->iahb_clk);
        clk_disable_unprepare(hdmi->isfr_clk);
-       i2c_put_adapter(hdmi->ddc);
+
+       if (hdmi->i2c)
+               i2c_del_adapter(&hdmi->i2c->adap);
+       else
+               i2c_put_adapter(hdmi->ddc);
 }
 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>");
+MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
 MODULE_DESCRIPTION("DW HDMI transmitter driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("platform:dw-hdmi");