Merge branch 'linux-linaro-lsk-v4.4-android' of git://git.linaro.org/kernel/linux...
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / bridge / dw-hdmi.c
index 9795b72472babc1a39c084fb69562ca7ac636e06..0ddc7b7da990492a7b575f62b9a637e67c35dab0 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>
@@ -53,6 +56,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 +160,17 @@ 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;
+};
+
 struct dw_hdmi {
        struct drm_connector connector;
        struct drm_encoder *encoder;
@@ -111,6 +181,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;
@@ -198,6 +269,202 @@ 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);
+               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);
+       }
+
+       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].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",
+                               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;
+
+       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 (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 +484,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 +604,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
@@ -833,7 +1157,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
        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 */
@@ -1246,26 +1570,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;
@@ -1541,16 +1845,40 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
        .mode_set = dw_hdmi_bridge_mode_set,
 };
 
+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)
@@ -1587,7 +1915,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.
@@ -1646,6 +1974,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);
@@ -1673,10 +2002,11 @@ 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)
@@ -1726,6 +2056,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);
@@ -1778,33 +2115,40 @@ 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);
 
-       /* Clear Hotplug interrupts */
-       hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE,
-                   HDMI_IH_PHY_STAT0);
+       hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
+                   HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
+                   HDMI_PHY_I2CM_CTLINT_ADDR);
 
-       ret = dw_hdmi_fb_registered(hdmi);
-       if (ret)
-               goto err_iahb;
+       /* Re-init HPD polarity */
+       hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0);
+
+       /* 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 */
        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;
@@ -1816,6 +2160,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);
@@ -1823,6 +2179,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);
@@ -1846,13 +2205,18 @@ void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
 
        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);
 
 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");