CHROMIUM: drm: bridge/dw_hdmi: Reorder soft reset of i2c
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / bridge / dw-hdmi.c
index 298018a8562ef38f6f50b6365c4e230d8efcec26..502c8355cdc08d28600d22f83eb67da67d4cb1f8 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>
@@ -22,6 +25,7 @@
 
 #include <drm/drm_of.h>
 #include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_encoder_slave.h>
@@ -52,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 },
@@ -100,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;
@@ -110,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;
@@ -197,6 +269,201 @@ 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_writeb(hdmi, 0x03, 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)
 {
@@ -216,60 +483,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;
 
-       while (freq > 48000) {
-               mult *= 2;
-               freq /= 2;
+       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;
+                       }
+               }
        }
 
+       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);
+
+       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;
 
-       return 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,
@@ -279,7 +603,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
@@ -832,7 +1156,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 */
@@ -1390,13 +1714,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;
@@ -1514,7 +1831,7 @@ static void dw_hdmi_connector_force(struct drm_connector *connector)
        mutex_unlock(&hdmi->mutex);
 }
 
-static struct drm_connector_funcs dw_hdmi_connector_funcs = {
+static const struct drm_connector_funcs dw_hdmi_connector_funcs = {
        .dpms = drm_helper_connector_dpms,
        .fill_modes = drm_helper_probe_single_connector_modes,
        .detect = dw_hdmi_connector_detect,
@@ -1522,31 +1839,65 @@ static struct drm_connector_funcs dw_hdmi_connector_funcs = {
        .force = dw_hdmi_connector_force,
 };
 
-static struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
+static const struct drm_connector_funcs dw_hdmi_atomic_connector_funcs = {
+       .dpms = drm_atomic_helper_connector_dpms,
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .detect = dw_hdmi_connector_detect,
+       .destroy = dw_hdmi_connector_destroy,
+       .force = dw_hdmi_connector_force,
+       .reset = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
        .get_modes = dw_hdmi_connector_get_modes,
        .mode_valid = dw_hdmi_connector_mode_valid,
        .best_encoder = dw_hdmi_connector_best_encoder,
 };
 
-static struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
+static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
        .enable = dw_hdmi_bridge_enable,
        .disable = dw_hdmi_bridge_disable,
        .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)
@@ -1583,7 +1934,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.
@@ -1642,13 +1993,19 @@ 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);
-       drm_connector_init(drm, &hdmi->connector, &dw_hdmi_connector_funcs,
-                          DRM_MODE_CONNECTOR_HDMIA);
 
-       hdmi->connector.encoder = encoder;
+       if (drm_core_check_feature(drm, DRIVER_ATOMIC))
+               drm_connector_init(drm, &hdmi->connector,
+                                  &dw_hdmi_atomic_connector_funcs,
+                                  DRM_MODE_CONNECTOR_HDMIA);
+       else
+               drm_connector_init(drm, &hdmi->connector,
+                                  &dw_hdmi_connector_funcs,
+                                  DRM_MODE_CONNECTOR_HDMIA);
 
        drm_mode_connector_attach_encoder(&hdmi->connector, encoder);
 
@@ -1664,10 +2021,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)
@@ -1717,6 +2075,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);
@@ -1791,11 +2156,20 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        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;
@@ -1807,6 +2181,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);
@@ -1814,6 +2200,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);
@@ -1837,13 +2226,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");