Merge branch 'drm-tda998x-devel' of git://git.armlinux.org.uk/~rmk/linux-arm into...
authorDave Airlie <airlied@redhat.com>
Thu, 24 Nov 2016 23:55:33 +0000 (09:55 +1000)
committerDave Airlie <airlied@redhat.com>
Thu, 24 Nov 2016 23:55:33 +0000 (09:55 +1000)
These updates:
* improve the robustness of the driver wrt races
* improve the compliance for sending infoframes and audio
* re-organise the function order in the driver to group like functions
  together.  (This unfortunately causes a conflict with the change in
  drm-misc, but it should be trivial to solve, although it looks more
  scarey than it really is - sfr has already sent two reports about
  this, one earlier today.)
* simplify tda998x_audio_get_eld and DPMS handling
* power down sections of the chip that we never use
* add some initial preparation for supporting the CEC driver

* 'drm-tda998x-devel' of git://git.armlinux.org.uk/~rmk/linux-arm:
  drm/i2c: tda998x: fix spelling mistake
  drm/i2c: tda998x: allow sharing of the CEC device accesses
  drm/i2c: tda998x: allow interrupt to be shared
  drm/i2c: tda998x: power down pre-filter and color conversion
  drm/i2c: tda998x: switch to boolean is_on
  drm/i2c: tda998x: remove complexity from tda998x_audio_get_eld()
  drm/i2c: tda998x: group audio functions together
  drm/i2c: tda998x: separate connector initialisation
  drm/i2c: tda998x: group connector functions and funcs together
  drm/i2c: tda998x: move and rename tda998x_encoder_set_config()
  drm/i2c: tda998x: correct function name in comments
  drm/i2c: tda998x: only enable audio if supported by sink
  drm/i2c: tda998x: only configure infoframes and audio if supported
  drm/i2c: tda998x: avoid race when programming audio
  drm/i2c: tda998x: avoid racy access to mode clock
  drm/i2c: tda998x: avoid race in tda998x_encoder_mode_set()
  drm/i2c: tda998x: move audio mutex initialisation

drivers/gpu/drm/i2c/tda998x_drv.c

index 027521f30b6ebc3f1c5bf1f9afa5f77a91b8a2c1..86f47e190309aea31bfdbacfc532f7df3307221c 100644 (file)
@@ -41,12 +41,15 @@ struct tda998x_priv {
        struct i2c_client *hdmi;
        struct mutex mutex;
        u16 rev;
+       u8 cec_addr;
        u8 current_page;
-       int dpms;
-       bool is_hdmi_sink;
+       bool is_on;
+       bool supports_infoframes;
+       bool sink_has_audio;
        u8 vip_cntrl_0;
        u8 vip_cntrl_1;
        u8 vip_cntrl_2;
+       unsigned long tmds_clock;
        struct tda998x_audio_params audio_params;
 
        struct platform_device *audio_pdev;
@@ -105,6 +108,8 @@ struct tda998x_priv {
 # define I2C_MASTER_DIS_FILT      (1 << 1)
 # define I2C_MASTER_APP_STRT_LAT  (1 << 2)
 #define REG_FEAT_POWERDOWN        REG(0x00, 0x0e)     /* read/write */
+# define FEAT_POWERDOWN_PREFILT   BIT(0)
+# define FEAT_POWERDOWN_CSC       BIT(1)
 # define FEAT_POWERDOWN_SPDIF     (1 << 3)
 #define REG_INT_FLAGS_0           REG(0x00, 0x0f)     /* read/write */
 #define REG_INT_FLAGS_1           REG(0x00, 0x10)     /* read/write */
@@ -370,35 +375,46 @@ struct tda998x_priv {
 static void
 cec_write(struct tda998x_priv *priv, u16 addr, u8 val)
 {
-       struct i2c_client *client = priv->cec;
        u8 buf[] = {addr, val};
+       struct i2c_msg msg = {
+               .addr = priv->cec_addr,
+               .len = 2,
+               .buf = buf,
+       };
        int ret;
 
-       ret = i2c_master_send(client, buf, sizeof(buf));
+       ret = i2c_transfer(priv->hdmi->adapter, &msg, 1);
        if (ret < 0)
-               dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
+               dev_err(&priv->hdmi->dev, "Error %d writing to cec:0x%x\n",
+                       ret, addr);
 }
 
 static u8
 cec_read(struct tda998x_priv *priv, u8 addr)
 {
-       struct i2c_client *client = priv->cec;
        u8 val;
+       struct i2c_msg msg[2] = {
+               {
+                       .addr = priv->cec_addr,
+                       .len = 1,
+                       .buf = &addr,
+               }, {
+                       .addr = priv->cec_addr,
+                       .flags = I2C_M_RD,
+                       .len = 1,
+                       .buf = &val,
+               },
+       };
        int ret;
 
-       ret = i2c_master_send(client, &addr, sizeof(addr));
-       if (ret < 0)
-               goto fail;
-
-       ret = i2c_master_recv(client, &val, sizeof(val));
-       if (ret < 0)
-               goto fail;
+       ret = i2c_transfer(priv->hdmi->adapter, msg, ARRAY_SIZE(msg));
+       if (ret < 0) {
+               dev_err(&priv->hdmi->dev, "Error %d reading from cec:0x%x\n",
+                       ret, addr);
+               val = 0;
+       }
 
        return val;
-
-fail:
-       dev_err(&client->dev, "Error %d reading from cec:0x%x\n", ret, addr);
-       return 0;
 }
 
 static int
@@ -579,9 +595,9 @@ tda998x_reset(struct tda998x_priv *priv)
  * HPD assertion: it needs a delay of 100ms to avoid timing out while
  * trying to read EDID data.
  *
- * However, tda998x_encoder_get_modes() may be called at any moment
+ * However, tda998x_connector_get_modes() may be called at any moment
  * after tda998x_connector_detect() indicates that we are connected, so
- * we need to delay probing modes in tda998x_encoder_get_modes() after
+ * we need to delay probing modes in tda998x_connector_get_modes() after
  * we have seen a HPD inactive->active transition.  This code implements
  * that delay.
  */
@@ -630,28 +646,30 @@ static irqreturn_t tda998x_irq_thread(int irq, void *data)
        bool handled = false;
 
        sta = cec_read(priv, REG_CEC_INTSTATUS);
-       cec = cec_read(priv, REG_CEC_RXSHPDINT);
-       lvl = cec_read(priv, REG_CEC_RXSHPDLEV);
-       flag0 = reg_read(priv, REG_INT_FLAGS_0);
-       flag1 = reg_read(priv, REG_INT_FLAGS_1);
-       flag2 = reg_read(priv, REG_INT_FLAGS_2);
-       DRM_DEBUG_DRIVER(
-               "tda irq sta %02x cec %02x lvl %02x f0 %02x f1 %02x f2 %02x\n",
-               sta, cec, lvl, flag0, flag1, flag2);
-
-       if (cec & CEC_RXSHPDINT_HPD) {
-               if (lvl & CEC_RXSHPDLEV_HPD)
-                       tda998x_edid_delay_start(priv);
-               else
-                       schedule_work(&priv->detect_work);
-
-               handled = true;
-       }
+       if (sta & CEC_INTSTATUS_HDMI) {
+               cec = cec_read(priv, REG_CEC_RXSHPDINT);
+               lvl = cec_read(priv, REG_CEC_RXSHPDLEV);
+               flag0 = reg_read(priv, REG_INT_FLAGS_0);
+               flag1 = reg_read(priv, REG_INT_FLAGS_1);
+               flag2 = reg_read(priv, REG_INT_FLAGS_2);
+               DRM_DEBUG_DRIVER(
+                       "tda irq sta %02x cec %02x lvl %02x f0 %02x f1 %02x f2 %02x\n",
+                       sta, cec, lvl, flag0, flag1, flag2);
+
+               if (cec & CEC_RXSHPDINT_HPD) {
+                       if (lvl & CEC_RXSHPDLEV_HPD)
+                               tda998x_edid_delay_start(priv);
+                       else
+                               schedule_work(&priv->detect_work);
+
+                       handled = true;
+               }
 
-       if ((flag2 & INT_FLAGS_2_EDID_BLK_RD) && priv->wq_edid_wait) {
-               priv->wq_edid_wait = 0;
-               wake_up(&priv->wq_edid);
-               handled = true;
+               if ((flag2 & INT_FLAGS_2_EDID_BLK_RD) && priv->wq_edid_wait) {
+                       priv->wq_edid_wait = 0;
+                       wake_up(&priv->wq_edid);
+                       handled = true;
+               }
        }
 
        return IRQ_RETVAL(handled);
@@ -700,6 +718,8 @@ tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode)
        tda998x_write_if(priv, DIP_IF_FLAGS_IF2, REG_IF2_HB0, &frame);
 }
 
+/* Audio support */
+
 static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
 {
        if (on) {
@@ -713,8 +733,7 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
 
 static int
 tda998x_configure_audio(struct tda998x_priv *priv,
-                       struct tda998x_audio_params *params,
-                       unsigned mode_clock)
+                       struct tda998x_audio_params *params)
 {
        u8 buf[6], clksel_aip, clksel_fs, cts_n, adiv;
        u32 n;
@@ -771,7 +790,7 @@ tda998x_configure_audio(struct tda998x_priv *priv,
         * assume 100MHz requires larger divider.
         */
        adiv = AUDIO_DIV_SERCLK_8;
-       if (mode_clock > 100000)
+       if (priv->tmds_clock > 100000)
                adiv++;                 /* AUDIO_DIV_SERCLK_16 */
 
        /* S/PDIF asks for a larger divider */
@@ -819,269 +838,164 @@ tda998x_configure_audio(struct tda998x_priv *priv,
        return tda998x_write_aif(priv, &params->cea);
 }
 
-/* DRM encoder functions */
-
-static void tda998x_encoder_set_config(struct tda998x_priv *priv,
-                                      const struct tda998x_encoder_params *p)
-{
-       priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) |
-                           (p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) |
-                           VIP_CNTRL_0_SWAP_B(p->swap_b) |
-                           (p->mirr_b ? VIP_CNTRL_0_MIRR_B : 0);
-       priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(p->swap_c) |
-                           (p->mirr_c ? VIP_CNTRL_1_MIRR_C : 0) |
-                           VIP_CNTRL_1_SWAP_D(p->swap_d) |
-                           (p->mirr_d ? VIP_CNTRL_1_MIRR_D : 0);
-       priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(p->swap_e) |
-                           (p->mirr_e ? VIP_CNTRL_2_MIRR_E : 0) |
-                           VIP_CNTRL_2_SWAP_F(p->swap_f) |
-                           (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
-
-       priv->audio_params = p->audio_params;
-}
-
-static void tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
+static int tda998x_audio_hw_params(struct device *dev, void *data,
+                                  struct hdmi_codec_daifmt *daifmt,
+                                  struct hdmi_codec_params *params)
 {
-       struct tda998x_priv *priv = enc_to_tda998x_priv(encoder);
-
-       /* we only care about on or off: */
-       if (mode != DRM_MODE_DPMS_ON)
-               mode = DRM_MODE_DPMS_OFF;
+       struct tda998x_priv *priv = dev_get_drvdata(dev);
+       int i, ret;
+       struct tda998x_audio_params audio = {
+               .sample_width = params->sample_width,
+               .sample_rate = params->sample_rate,
+               .cea = params->cea,
+       };
 
-       if (mode == priv->dpms)
-               return;
+       memcpy(audio.status, params->iec.status,
+              min(sizeof(audio.status), sizeof(params->iec.status)));
 
-       switch (mode) {
-       case DRM_MODE_DPMS_ON:
-               /* enable video ports, audio will be enabled later */
-               reg_write(priv, REG_ENA_VP_0, 0xff);
-               reg_write(priv, REG_ENA_VP_1, 0xff);
-               reg_write(priv, REG_ENA_VP_2, 0xff);
-               /* set muxing after enabling ports: */
-               reg_write(priv, REG_VIP_CNTRL_0, priv->vip_cntrl_0);
-               reg_write(priv, REG_VIP_CNTRL_1, priv->vip_cntrl_1);
-               reg_write(priv, REG_VIP_CNTRL_2, priv->vip_cntrl_2);
+       switch (daifmt->fmt) {
+       case HDMI_I2S:
+               if (daifmt->bit_clk_inv || daifmt->frame_clk_inv ||
+                   daifmt->bit_clk_master || daifmt->frame_clk_master) {
+                       dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__,
+                               daifmt->bit_clk_inv, daifmt->frame_clk_inv,
+                               daifmt->bit_clk_master,
+                               daifmt->frame_clk_master);
+                       return -EINVAL;
+               }
+               for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++)
+                       if (priv->audio_port[i].format == AFMT_I2S)
+                               audio.config = priv->audio_port[i].config;
+               audio.format = AFMT_I2S;
                break;
-       case DRM_MODE_DPMS_OFF:
-               /* disable video ports */
-               reg_write(priv, REG_ENA_VP_0, 0x00);
-               reg_write(priv, REG_ENA_VP_1, 0x00);
-               reg_write(priv, REG_ENA_VP_2, 0x00);
+       case HDMI_SPDIF:
+               for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++)
+                       if (priv->audio_port[i].format == AFMT_SPDIF)
+                               audio.config = priv->audio_port[i].config;
+               audio.format = AFMT_SPDIF;
                break;
+       default:
+               dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
+               return -EINVAL;
        }
 
-       priv->dpms = mode;
-}
+       if (audio.config == 0) {
+               dev_err(dev, "%s: No audio configuration found\n", __func__);
+               return -EINVAL;
+       }
 
-static int tda998x_connector_mode_valid(struct drm_connector *connector,
-                                       struct drm_display_mode *mode)
-{
-       /* TDA19988 dotclock can go up to 165MHz */
-       struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
+       mutex_lock(&priv->audio_mutex);
+       if (priv->supports_infoframes && priv->sink_has_audio)
+               ret = tda998x_configure_audio(priv, &audio);
+       else
+               ret = 0;
 
-       if (mode->clock > ((priv->rev == TDA19988) ? 165000 : 150000))
-               return MODE_CLOCK_HIGH;
-       if (mode->htotal >= BIT(13))
-               return MODE_BAD_HVALUE;
-       if (mode->vtotal >= BIT(11))
-               return MODE_BAD_VVALUE;
-       return MODE_OK;
+       if (ret == 0)
+               priv->audio_params = audio;
+       mutex_unlock(&priv->audio_mutex);
+
+       return ret;
 }
 
-static void
-tda998x_encoder_mode_set(struct drm_encoder *encoder,
-                        struct drm_display_mode *mode,
-                        struct drm_display_mode *adjusted_mode)
+static void tda998x_audio_shutdown(struct device *dev, void *data)
 {
-       struct tda998x_priv *priv = enc_to_tda998x_priv(encoder);
-       u16 ref_pix, ref_line, n_pix, n_line;
-       u16 hs_pix_s, hs_pix_e;
-       u16 vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e;
-       u16 vs2_pix_s, vs2_pix_e, vs2_line_s, vs2_line_e;
-       u16 vwin1_line_s, vwin1_line_e;
-       u16 vwin2_line_s, vwin2_line_e;
-       u16 de_pix_s, de_pix_e;
-       u8 reg, div, rep;
-
-       /*
-        * Internally TDA998x is using ITU-R BT.656 style sync but
-        * we get VESA style sync. TDA998x is using a reference pixel
-        * relative to ITU to sync to the input frame and for output
-        * sync generation. Currently, we are using reference detection
-        * from HS/VS, i.e. REFPIX/REFLINE denote frame start sync point
-        * which is position of rising VS with coincident rising HS.
-        *
-        * Now there is some issues to take care of:
-        * - HDMI data islands require sync-before-active
-        * - TDA998x register values must be > 0 to be enabled
-        * - REFLINE needs an additional offset of +1
-        * - REFPIX needs an addtional offset of +1 for UYUV and +3 for RGB
-        *
-        * So we add +1 to all horizontal and vertical register values,
-        * plus an additional +3 for REFPIX as we are using RGB input only.
-        */
-       n_pix        = mode->htotal;
-       n_line       = mode->vtotal;
+       struct tda998x_priv *priv = dev_get_drvdata(dev);
 
-       hs_pix_e     = mode->hsync_end - mode->hdisplay;
-       hs_pix_s     = mode->hsync_start - mode->hdisplay;
-       de_pix_e     = mode->htotal;
-       de_pix_s     = mode->htotal - mode->hdisplay;
-       ref_pix      = 3 + hs_pix_s;
+       mutex_lock(&priv->audio_mutex);
 
-       /*
-        * Attached LCD controllers may generate broken sync. Allow
-        * those to adjust the position of the rising VS edge by adding
-        * HSKEW to ref_pix.
-        */
-       if (adjusted_mode->flags & DRM_MODE_FLAG_HSKEW)
-               ref_pix += adjusted_mode->hskew;
+       reg_write(priv, REG_ENA_AP, 0);
 
-       if ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0) {
-               ref_line     = 1 + mode->vsync_start - mode->vdisplay;
-               vwin1_line_s = mode->vtotal - mode->vdisplay - 1;
-               vwin1_line_e = vwin1_line_s + mode->vdisplay;
-               vs1_pix_s    = vs1_pix_e = hs_pix_s;
-               vs1_line_s   = mode->vsync_start - mode->vdisplay;
-               vs1_line_e   = vs1_line_s +
-                              mode->vsync_end - mode->vsync_start;
-               vwin2_line_s = vwin2_line_e = 0;
-               vs2_pix_s    = vs2_pix_e  = 0;
-               vs2_line_s   = vs2_line_e = 0;
-       } else {
-               ref_line     = 1 + (mode->vsync_start - mode->vdisplay)/2;
-               vwin1_line_s = (mode->vtotal - mode->vdisplay)/2;
-               vwin1_line_e = vwin1_line_s + mode->vdisplay/2;
-               vs1_pix_s    = vs1_pix_e = hs_pix_s;
-               vs1_line_s   = (mode->vsync_start - mode->vdisplay)/2;
-               vs1_line_e   = vs1_line_s +
-                              (mode->vsync_end - mode->vsync_start)/2;
-               vwin2_line_s = vwin1_line_s + mode->vtotal/2;
-               vwin2_line_e = vwin2_line_s + mode->vdisplay/2;
-               vs2_pix_s    = vs2_pix_e = hs_pix_s + mode->htotal/2;
-               vs2_line_s   = vs1_line_s + mode->vtotal/2 ;
-               vs2_line_e   = vs2_line_s +
-                              (mode->vsync_end - mode->vsync_start)/2;
-       }
+       priv->audio_params.format = AFMT_UNUSED;
 
-       div = 148500 / mode->clock;
-       if (div != 0) {
-               div--;
-               if (div > 3)
-                       div = 3;
-       }
+       mutex_unlock(&priv->audio_mutex);
+}
 
-       /* mute the audio FIFO: */
-       reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+int tda998x_audio_digital_mute(struct device *dev, void *data, bool enable)
+{
+       struct tda998x_priv *priv = dev_get_drvdata(dev);
 
-       /* set HDMI HDCP mode off: */
-       reg_write(priv, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
-       reg_clear(priv, REG_TX33, TX33_HDMI);
-       reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
+       mutex_lock(&priv->audio_mutex);
 
-       /* no pre-filter or interpolator: */
-       reg_write(priv, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
-                       HVF_CNTRL_0_INTPOL(0));
-       reg_write(priv, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
-       reg_write(priv, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
-                       VIP_CNTRL_4_BLC(0));
+       tda998x_audio_mute(priv, enable);
 
-       reg_clear(priv, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
-       reg_clear(priv, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR |
-                                         PLL_SERIAL_3_SRL_DE);
-       reg_write(priv, REG_SERIALIZER, 0);
-       reg_write(priv, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
+       mutex_unlock(&priv->audio_mutex);
+       return 0;
+}
 
-       /* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
-       rep = 0;
-       reg_write(priv, REG_RPT_CNTRL, 0);
-       reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
-                       SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+static int tda998x_audio_get_eld(struct device *dev, void *data,
+                                uint8_t *buf, size_t len)
+{
+       struct tda998x_priv *priv = dev_get_drvdata(dev);
 
-       reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
-                       PLL_SERIAL_2_SRL_PR(rep));
+       mutex_lock(&priv->audio_mutex);
+       memcpy(buf, priv->connector.eld,
+              min(sizeof(priv->connector.eld), len));
+       mutex_unlock(&priv->audio_mutex);
 
-       /* set color matrix bypass flag: */
-       reg_write(priv, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP |
-                               MAT_CONTRL_MAT_SC(1));
+       return 0;
+}
 
-       /* set BIAS tmds value: */
-       reg_write(priv, REG_ANA_GENERAL, 0x09);
+static const struct hdmi_codec_ops audio_codec_ops = {
+       .hw_params = tda998x_audio_hw_params,
+       .audio_shutdown = tda998x_audio_shutdown,
+       .digital_mute = tda998x_audio_digital_mute,
+       .get_eld = tda998x_audio_get_eld,
+};
 
-       /*
-        * Sync on rising HSYNC/VSYNC
-        */
-       reg = VIP_CNTRL_3_SYNC_HS;
+static int tda998x_audio_codec_init(struct tda998x_priv *priv,
+                                   struct device *dev)
+{
+       struct hdmi_codec_pdata codec_data = {
+               .ops = &audio_codec_ops,
+               .max_i2s_channels = 2,
+       };
+       int i;
 
-       /*
-        * TDA19988 requires high-active sync at input stage,
-        * so invert low-active sync provided by master encoder here
-        */
-       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
-               reg |= VIP_CNTRL_3_H_TGL;
-       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
-               reg |= VIP_CNTRL_3_V_TGL;
-       reg_write(priv, REG_VIP_CNTRL_3, reg);
+       for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) {
+               if (priv->audio_port[i].format == AFMT_I2S &&
+                   priv->audio_port[i].config != 0)
+                       codec_data.i2s = 1;
+               if (priv->audio_port[i].format == AFMT_SPDIF &&
+                   priv->audio_port[i].config != 0)
+                       codec_data.spdif = 1;
+       }
 
-       reg_write(priv, REG_VIDFORMAT, 0x00);
-       reg_write16(priv, REG_REFPIX_MSB, ref_pix);
-       reg_write16(priv, REG_REFLINE_MSB, ref_line);
-       reg_write16(priv, REG_NPIX_MSB, n_pix);
-       reg_write16(priv, REG_NLINE_MSB, n_line);
-       reg_write16(priv, REG_VS_LINE_STRT_1_MSB, vs1_line_s);
-       reg_write16(priv, REG_VS_PIX_STRT_1_MSB, vs1_pix_s);
-       reg_write16(priv, REG_VS_LINE_END_1_MSB, vs1_line_e);
-       reg_write16(priv, REG_VS_PIX_END_1_MSB, vs1_pix_e);
-       reg_write16(priv, REG_VS_LINE_STRT_2_MSB, vs2_line_s);
-       reg_write16(priv, REG_VS_PIX_STRT_2_MSB, vs2_pix_s);
-       reg_write16(priv, REG_VS_LINE_END_2_MSB, vs2_line_e);
-       reg_write16(priv, REG_VS_PIX_END_2_MSB, vs2_pix_e);
-       reg_write16(priv, REG_HS_PIX_START_MSB, hs_pix_s);
-       reg_write16(priv, REG_HS_PIX_STOP_MSB, hs_pix_e);
-       reg_write16(priv, REG_VWIN_START_1_MSB, vwin1_line_s);
-       reg_write16(priv, REG_VWIN_END_1_MSB, vwin1_line_e);
-       reg_write16(priv, REG_VWIN_START_2_MSB, vwin2_line_s);
-       reg_write16(priv, REG_VWIN_END_2_MSB, vwin2_line_e);
-       reg_write16(priv, REG_DE_START_MSB, de_pix_s);
-       reg_write16(priv, REG_DE_STOP_MSB, de_pix_e);
+       priv->audio_pdev = platform_device_register_data(
+               dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
+               &codec_data, sizeof(codec_data));
 
-       if (priv->rev == TDA19988) {
-               /* let incoming pixels fill the active space (if any) */
-               reg_write(priv, REG_ENABLE_SPACE, 0x00);
-       }
+       return PTR_ERR_OR_ZERO(priv->audio_pdev);
+}
 
-       /*
-        * Always generate sync polarity relative to input sync and
-        * revert input stage toggled sync at output stage
-        */
-       reg = TBG_CNTRL_1_DWIN_DIS | TBG_CNTRL_1_TGL_EN;
-       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
-               reg |= TBG_CNTRL_1_H_TGL;
-       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
-               reg |= TBG_CNTRL_1_V_TGL;
-       reg_write(priv, REG_TBG_CNTRL_1, reg);
+/* DRM connector functions */
 
-       /* must be last register set: */
-       reg_write(priv, REG_TBG_CNTRL_0, 0);
+static int tda998x_connector_dpms(struct drm_connector *connector, int mode)
+{
+       if (drm_core_check_feature(connector->dev, DRIVER_ATOMIC))
+               return drm_atomic_helper_connector_dpms(connector, mode);
+       else
+               return drm_helper_connector_dpms(connector, mode);
+}
 
-       /* Only setup the info frames if the sink is HDMI */
-       if (priv->is_hdmi_sink) {
-               /* We need to turn HDMI HDCP stuff on to get audio through */
-               reg &= ~TBG_CNTRL_1_DWIN_DIS;
-               reg_write(priv, REG_TBG_CNTRL_1, reg);
-               reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1));
-               reg_set(priv, REG_TX33, TX33_HDMI);
+static int tda998x_connector_fill_modes(struct drm_connector *connector,
+                                       uint32_t maxX, uint32_t maxY)
+{
+       struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
+       int ret;
 
-               tda998x_write_avi(priv, adjusted_mode);
+       mutex_lock(&priv->audio_mutex);
+       ret = drm_helper_probe_single_connector_modes(connector, maxX, maxY);
 
-               if (priv->audio_params.format != AFMT_UNUSED) {
-                       mutex_lock(&priv->audio_mutex);
-                       tda998x_configure_audio(priv,
-                                               &priv->audio_params,
-                                               adjusted_mode->clock);
-                       mutex_unlock(&priv->audio_mutex);
-               }
+       if (connector->edid_blob_ptr) {
+               struct edid *edid = (void *)connector->edid_blob_ptr->data;
+
+               priv->sink_has_audio = drm_detect_monitor_audio(edid);
+       } else {
+               priv->sink_has_audio = false;
        }
+       mutex_unlock(&priv->audio_mutex);
+
+       return ret;
 }
 
 static enum drm_connector_status
@@ -1094,6 +1008,21 @@ tda998x_connector_detect(struct drm_connector *connector, bool force)
                        connector_status_disconnected;
 }
 
+static void tda998x_connector_destroy(struct drm_connector *connector)
+{
+       drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs tda998x_connector_funcs = {
+       .dpms = tda998x_connector_dpms,
+       .reset = drm_atomic_helper_connector_reset,
+       .fill_modes = tda998x_connector_fill_modes,
+       .detect = tda998x_connector_detect,
+       .destroy = tda998x_connector_destroy,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
 static int read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length)
 {
        struct tda998x_priv *priv = data;
@@ -1179,7 +1108,6 @@ static int tda998x_connector_get_modes(struct drm_connector *connector)
 
        drm_mode_connector_update_edid_property(connector, edid);
        n = drm_add_edid_modes(connector, edid);
-       priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
        drm_edid_to_eld(connector, edid);
 
        kfree(edid);
@@ -1187,172 +1115,323 @@ static int tda998x_connector_get_modes(struct drm_connector *connector)
        return n;
 }
 
-static void tda998x_encoder_set_polling(struct tda998x_priv *priv,
-                                       struct drm_connector *connector)
+static int tda998x_connector_mode_valid(struct drm_connector *connector,
+                                       struct drm_display_mode *mode)
+{
+       /* TDA19988 dotclock can go up to 165MHz */
+       struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
+
+       if (mode->clock > ((priv->rev == TDA19988) ? 165000 : 150000))
+               return MODE_CLOCK_HIGH;
+       if (mode->htotal >= BIT(13))
+               return MODE_BAD_HVALUE;
+       if (mode->vtotal >= BIT(11))
+               return MODE_BAD_VVALUE;
+       return MODE_OK;
+}
+
+static struct drm_encoder *
+tda998x_connector_best_encoder(struct drm_connector *connector)
+{
+       struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
+
+       return &priv->encoder;
+}
+
+static
+const struct drm_connector_helper_funcs tda998x_connector_helper_funcs = {
+       .get_modes = tda998x_connector_get_modes,
+       .mode_valid = tda998x_connector_mode_valid,
+       .best_encoder = tda998x_connector_best_encoder,
+};
+
+static int tda998x_connector_init(struct tda998x_priv *priv,
+                                 struct drm_device *drm)
 {
+       struct drm_connector *connector = &priv->connector;
+       int ret;
+
+       connector->interlace_allowed = 1;
+
        if (priv->hdmi->irq)
                connector->polled = DRM_CONNECTOR_POLL_HPD;
        else
                connector->polled = DRM_CONNECTOR_POLL_CONNECT |
                        DRM_CONNECTOR_POLL_DISCONNECT;
+
+       drm_connector_helper_add(connector, &tda998x_connector_helper_funcs);
+       ret = drm_connector_init(drm, connector, &tda998x_connector_funcs,
+                                DRM_MODE_CONNECTOR_HDMIA);
+       if (ret)
+               return ret;
+
+       drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder);
+
+       return 0;
 }
 
-static void tda998x_destroy(struct tda998x_priv *priv)
+/* DRM encoder functions */
+
+static void tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
 {
-       /* disable all IRQs and free the IRQ handler */
-       cec_write(priv, REG_CEC_RXSHPDINTENA, 0);
-       reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+       struct tda998x_priv *priv = enc_to_tda998x_priv(encoder);
+       bool on;
 
-       if (priv->audio_pdev)
-               platform_device_unregister(priv->audio_pdev);
+       /* we only care about on or off: */
+       on = mode == DRM_MODE_DPMS_ON;
 
-       if (priv->hdmi->irq)
-               free_irq(priv->hdmi->irq, priv);
+       if (on == priv->is_on)
+               return;
 
-       del_timer_sync(&priv->edid_delay_timer);
-       cancel_work_sync(&priv->detect_work);
+       if (on) {
+               /* enable video ports, audio will be enabled later */
+               reg_write(priv, REG_ENA_VP_0, 0xff);
+               reg_write(priv, REG_ENA_VP_1, 0xff);
+               reg_write(priv, REG_ENA_VP_2, 0xff);
+               /* set muxing after enabling ports: */
+               reg_write(priv, REG_VIP_CNTRL_0, priv->vip_cntrl_0);
+               reg_write(priv, REG_VIP_CNTRL_1, priv->vip_cntrl_1);
+               reg_write(priv, REG_VIP_CNTRL_2, priv->vip_cntrl_2);
 
-       i2c_unregister_device(priv->cec);
+               priv->is_on = true;
+       } else {
+               /* disable video ports */
+               reg_write(priv, REG_ENA_VP_0, 0x00);
+               reg_write(priv, REG_ENA_VP_1, 0x00);
+               reg_write(priv, REG_ENA_VP_2, 0x00);
+
+               priv->is_on = false;
+       }
 }
 
-static int tda998x_audio_hw_params(struct device *dev, void *data,
-                                  struct hdmi_codec_daifmt *daifmt,
-                                  struct hdmi_codec_params *params)
+static void
+tda998x_encoder_mode_set(struct drm_encoder *encoder,
+                        struct drm_display_mode *mode,
+                        struct drm_display_mode *adjusted_mode)
 {
-       struct tda998x_priv *priv = dev_get_drvdata(dev);
-       int i, ret;
-       struct tda998x_audio_params audio = {
-               .sample_width = params->sample_width,
-               .sample_rate = params->sample_rate,
-               .cea = params->cea,
-       };
+       struct tda998x_priv *priv = enc_to_tda998x_priv(encoder);
+       u16 ref_pix, ref_line, n_pix, n_line;
+       u16 hs_pix_s, hs_pix_e;
+       u16 vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e;
+       u16 vs2_pix_s, vs2_pix_e, vs2_line_s, vs2_line_e;
+       u16 vwin1_line_s, vwin1_line_e;
+       u16 vwin2_line_s, vwin2_line_e;
+       u16 de_pix_s, de_pix_e;
+       u8 reg, div, rep;
 
-       if (!priv->encoder.crtc)
-               return -ENODEV;
+       /*
+        * Internally TDA998x is using ITU-R BT.656 style sync but
+        * we get VESA style sync. TDA998x is using a reference pixel
+        * relative to ITU to sync to the input frame and for output
+        * sync generation. Currently, we are using reference detection
+        * from HS/VS, i.e. REFPIX/REFLINE denote frame start sync point
+        * which is position of rising VS with coincident rising HS.
+        *
+        * Now there is some issues to take care of:
+        * - HDMI data islands require sync-before-active
+        * - TDA998x register values must be > 0 to be enabled
+        * - REFLINE needs an additional offset of +1
+        * - REFPIX needs an addtional offset of +1 for UYUV and +3 for RGB
+        *
+        * So we add +1 to all horizontal and vertical register values,
+        * plus an additional +3 for REFPIX as we are using RGB input only.
+        */
+       n_pix        = mode->htotal;
+       n_line       = mode->vtotal;
 
-       memcpy(audio.status, params->iec.status,
-              min(sizeof(audio.status), sizeof(params->iec.status)));
+       hs_pix_e     = mode->hsync_end - mode->hdisplay;
+       hs_pix_s     = mode->hsync_start - mode->hdisplay;
+       de_pix_e     = mode->htotal;
+       de_pix_s     = mode->htotal - mode->hdisplay;
+       ref_pix      = 3 + hs_pix_s;
 
-       switch (daifmt->fmt) {
-       case HDMI_I2S:
-               if (daifmt->bit_clk_inv || daifmt->frame_clk_inv ||
-                   daifmt->bit_clk_master || daifmt->frame_clk_master) {
-                       dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__,
-                               daifmt->bit_clk_inv, daifmt->frame_clk_inv,
-                               daifmt->bit_clk_master,
-                               daifmt->frame_clk_master);
-                       return -EINVAL;
-               }
-               for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++)
-                       if (priv->audio_port[i].format == AFMT_I2S)
-                               audio.config = priv->audio_port[i].config;
-               audio.format = AFMT_I2S;
-               break;
-       case HDMI_SPDIF:
-               for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++)
-                       if (priv->audio_port[i].format == AFMT_SPDIF)
-                               audio.config = priv->audio_port[i].config;
-               audio.format = AFMT_SPDIF;
-               break;
-       default:
-               dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt);
-               return -EINVAL;
+       /*
+        * Attached LCD controllers may generate broken sync. Allow
+        * those to adjust the position of the rising VS edge by adding
+        * HSKEW to ref_pix.
+        */
+       if (adjusted_mode->flags & DRM_MODE_FLAG_HSKEW)
+               ref_pix += adjusted_mode->hskew;
+
+       if ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0) {
+               ref_line     = 1 + mode->vsync_start - mode->vdisplay;
+               vwin1_line_s = mode->vtotal - mode->vdisplay - 1;
+               vwin1_line_e = vwin1_line_s + mode->vdisplay;
+               vs1_pix_s    = vs1_pix_e = hs_pix_s;
+               vs1_line_s   = mode->vsync_start - mode->vdisplay;
+               vs1_line_e   = vs1_line_s +
+                              mode->vsync_end - mode->vsync_start;
+               vwin2_line_s = vwin2_line_e = 0;
+               vs2_pix_s    = vs2_pix_e  = 0;
+               vs2_line_s   = vs2_line_e = 0;
+       } else {
+               ref_line     = 1 + (mode->vsync_start - mode->vdisplay)/2;
+               vwin1_line_s = (mode->vtotal - mode->vdisplay)/2;
+               vwin1_line_e = vwin1_line_s + mode->vdisplay/2;
+               vs1_pix_s    = vs1_pix_e = hs_pix_s;
+               vs1_line_s   = (mode->vsync_start - mode->vdisplay)/2;
+               vs1_line_e   = vs1_line_s +
+                              (mode->vsync_end - mode->vsync_start)/2;
+               vwin2_line_s = vwin1_line_s + mode->vtotal/2;
+               vwin2_line_e = vwin2_line_s + mode->vdisplay/2;
+               vs2_pix_s    = vs2_pix_e = hs_pix_s + mode->htotal/2;
+               vs2_line_s   = vs1_line_s + mode->vtotal/2 ;
+               vs2_line_e   = vs2_line_s +
+                              (mode->vsync_end - mode->vsync_start)/2;
        }
 
-       if (audio.config == 0) {
-               dev_err(dev, "%s: No audio configutation found\n", __func__);
-               return -EINVAL;
+       div = 148500 / mode->clock;
+       if (div != 0) {
+               div--;
+               if (div > 3)
+                       div = 3;
        }
 
        mutex_lock(&priv->audio_mutex);
-       ret = tda998x_configure_audio(priv,
-                                     &audio,
-                                     priv->encoder.crtc->hwmode.clock);
 
-       if (ret == 0)
-               priv->audio_params = audio;
-       mutex_unlock(&priv->audio_mutex);
+       /* mute the audio FIFO: */
+       reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
 
-       return ret;
-}
+       /* set HDMI HDCP mode off: */
+       reg_write(priv, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+       reg_clear(priv, REG_TX33, TX33_HDMI);
+       reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
 
-static void tda998x_audio_shutdown(struct device *dev, void *data)
-{
-       struct tda998x_priv *priv = dev_get_drvdata(dev);
+       /* no pre-filter or interpolator: */
+       reg_write(priv, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
+                       HVF_CNTRL_0_INTPOL(0));
+       reg_set(priv, REG_FEAT_POWERDOWN, FEAT_POWERDOWN_PREFILT);
+       reg_write(priv, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
+       reg_write(priv, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
+                       VIP_CNTRL_4_BLC(0));
 
-       mutex_lock(&priv->audio_mutex);
+       reg_clear(priv, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
+       reg_clear(priv, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR |
+                                         PLL_SERIAL_3_SRL_DE);
+       reg_write(priv, REG_SERIALIZER, 0);
+       reg_write(priv, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
 
-       reg_write(priv, REG_ENA_AP, 0);
+       /* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
+       rep = 0;
+       reg_write(priv, REG_RPT_CNTRL, 0);
+       reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
+                       SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
 
-       priv->audio_params.format = AFMT_UNUSED;
+       reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
+                       PLL_SERIAL_2_SRL_PR(rep));
 
-       mutex_unlock(&priv->audio_mutex);
-}
+       /* set color matrix bypass flag: */
+       reg_write(priv, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP |
+                               MAT_CONTRL_MAT_SC(1));
+       reg_set(priv, REG_FEAT_POWERDOWN, FEAT_POWERDOWN_CSC);
 
-static int
-tda998x_audio_digital_mute(struct device *dev, void *data, bool enable)
-{
-       struct tda998x_priv *priv = dev_get_drvdata(dev);
+       /* set BIAS tmds value: */
+       reg_write(priv, REG_ANA_GENERAL, 0x09);
 
-       mutex_lock(&priv->audio_mutex);
+       /*
+        * Sync on rising HSYNC/VSYNC
+        */
+       reg = VIP_CNTRL_3_SYNC_HS;
 
-       tda998x_audio_mute(priv, enable);
+       /*
+        * TDA19988 requires high-active sync at input stage,
+        * so invert low-active sync provided by master encoder here
+        */
+       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+               reg |= VIP_CNTRL_3_H_TGL;
+       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+               reg |= VIP_CNTRL_3_V_TGL;
+       reg_write(priv, REG_VIP_CNTRL_3, reg);
 
-       mutex_unlock(&priv->audio_mutex);
-       return 0;
-}
+       reg_write(priv, REG_VIDFORMAT, 0x00);
+       reg_write16(priv, REG_REFPIX_MSB, ref_pix);
+       reg_write16(priv, REG_REFLINE_MSB, ref_line);
+       reg_write16(priv, REG_NPIX_MSB, n_pix);
+       reg_write16(priv, REG_NLINE_MSB, n_line);
+       reg_write16(priv, REG_VS_LINE_STRT_1_MSB, vs1_line_s);
+       reg_write16(priv, REG_VS_PIX_STRT_1_MSB, vs1_pix_s);
+       reg_write16(priv, REG_VS_LINE_END_1_MSB, vs1_line_e);
+       reg_write16(priv, REG_VS_PIX_END_1_MSB, vs1_pix_e);
+       reg_write16(priv, REG_VS_LINE_STRT_2_MSB, vs2_line_s);
+       reg_write16(priv, REG_VS_PIX_STRT_2_MSB, vs2_pix_s);
+       reg_write16(priv, REG_VS_LINE_END_2_MSB, vs2_line_e);
+       reg_write16(priv, REG_VS_PIX_END_2_MSB, vs2_pix_e);
+       reg_write16(priv, REG_HS_PIX_START_MSB, hs_pix_s);
+       reg_write16(priv, REG_HS_PIX_STOP_MSB, hs_pix_e);
+       reg_write16(priv, REG_VWIN_START_1_MSB, vwin1_line_s);
+       reg_write16(priv, REG_VWIN_END_1_MSB, vwin1_line_e);
+       reg_write16(priv, REG_VWIN_START_2_MSB, vwin2_line_s);
+       reg_write16(priv, REG_VWIN_END_2_MSB, vwin2_line_e);
+       reg_write16(priv, REG_DE_START_MSB, de_pix_s);
+       reg_write16(priv, REG_DE_STOP_MSB, de_pix_e);
 
-static int tda998x_audio_get_eld(struct device *dev, void *data,
-                                uint8_t *buf, size_t len)
-{
-       struct tda998x_priv *priv = dev_get_drvdata(dev);
-       struct drm_mode_config *config = &priv->encoder.dev->mode_config;
-       struct drm_connector *connector;
-       int ret = -ENODEV;
-
-       mutex_lock(&config->mutex);
-       list_for_each_entry(connector, &config->connector_list, head) {
-               if (&priv->encoder == connector->encoder) {
-                       memcpy(buf, connector->eld,
-                              min(sizeof(connector->eld), len));
-                       ret = 0;
-               }
+       if (priv->rev == TDA19988) {
+               /* let incoming pixels fill the active space (if any) */
+               reg_write(priv, REG_ENABLE_SPACE, 0x00);
        }
-       mutex_unlock(&config->mutex);
 
-       return ret;
-}
+       /*
+        * Always generate sync polarity relative to input sync and
+        * revert input stage toggled sync at output stage
+        */
+       reg = TBG_CNTRL_1_DWIN_DIS | TBG_CNTRL_1_TGL_EN;
+       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+               reg |= TBG_CNTRL_1_H_TGL;
+       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+               reg |= TBG_CNTRL_1_V_TGL;
+       reg_write(priv, REG_TBG_CNTRL_1, reg);
 
-static const struct hdmi_codec_ops audio_codec_ops = {
-       .hw_params = tda998x_audio_hw_params,
-       .audio_shutdown = tda998x_audio_shutdown,
-       .digital_mute = tda998x_audio_digital_mute,
-       .get_eld = tda998x_audio_get_eld,
-};
+       /* must be last register set: */
+       reg_write(priv, REG_TBG_CNTRL_0, 0);
 
-static int tda998x_audio_codec_init(struct tda998x_priv *priv,
-                                   struct device *dev)
-{
-       struct hdmi_codec_pdata codec_data = {
-               .ops = &audio_codec_ops,
-               .max_i2s_channels = 2,
-       };
-       int i;
+       priv->tmds_clock = adjusted_mode->clock;
 
-       for (i = 0; i < ARRAY_SIZE(priv->audio_port); i++) {
-               if (priv->audio_port[i].format == AFMT_I2S &&
-                   priv->audio_port[i].config != 0)
-                       codec_data.i2s = 1;
-               if (priv->audio_port[i].format == AFMT_SPDIF &&
-                   priv->audio_port[i].config != 0)
-                       codec_data.spdif = 1;
+       /* CEA-861B section 6 says that:
+        * CEA version 1 (CEA-861) has no support for infoframes.
+        * CEA version 2 (CEA-861A) supports version 1 AVI infoframes,
+        * and optional basic audio.
+        * CEA version 3 (CEA-861B) supports version 1 and 2 AVI infoframes,
+        * and optional digital audio, with audio infoframes.
+        *
+        * Since we only support generation of version 2 AVI infoframes,
+        * ignore CEA version 2 and below (iow, behave as if we're a
+        * CEA-861 source.)
+        */
+       priv->supports_infoframes = priv->connector.display_info.cea_rev >= 3;
+
+       if (priv->supports_infoframes) {
+               /* We need to turn HDMI HDCP stuff on to get audio through */
+               reg &= ~TBG_CNTRL_1_DWIN_DIS;
+               reg_write(priv, REG_TBG_CNTRL_1, reg);
+               reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1));
+               reg_set(priv, REG_TX33, TX33_HDMI);
+
+               tda998x_write_avi(priv, adjusted_mode);
+
+               if (priv->audio_params.format != AFMT_UNUSED &&
+                   priv->sink_has_audio)
+                       tda998x_configure_audio(priv, &priv->audio_params);
        }
 
-       priv->audio_pdev = platform_device_register_data(
-               dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
-               &codec_data, sizeof(codec_data));
+       mutex_unlock(&priv->audio_mutex);
+}
 
-       return PTR_ERR_OR_ZERO(priv->audio_pdev);
+static void tda998x_destroy(struct tda998x_priv *priv)
+{
+       /* disable all IRQs and free the IRQ handler */
+       cec_write(priv, REG_CEC_RXSHPDINTENA, 0);
+       reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+       if (priv->audio_pdev)
+               platform_device_unregister(priv->audio_pdev);
+
+       if (priv->hdmi->irq)
+               free_irq(priv->hdmi->irq, priv);
+
+       del_timer_sync(&priv->edid_delay_timer);
+       cancel_work_sync(&priv->detect_work);
+
+       i2c_unregister_device(priv->cec);
 }
 
 /* I2C driver functions */
@@ -1404,22 +1483,21 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
        struct device_node *np = client->dev.of_node;
        u32 video;
        int rev_lo, rev_hi, ret;
-       unsigned short cec_addr;
+
+       mutex_init(&priv->audio_mutex); /* Protect access from audio thread */
 
        priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3);
        priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1);
        priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5);
 
+       /* CEC I2C address bound to TDA998x I2C addr by configuration pins */
+       priv->cec_addr = 0x34 + (client->addr & 0x03);
        priv->current_page = 0xff;
        priv->hdmi = client;
-       /* CEC I2C address bound to TDA998x I2C addr by configuration pins */
-       cec_addr = 0x34 + (client->addr & 0x03);
-       priv->cec = i2c_new_dummy(client->adapter, cec_addr);
+       priv->cec = i2c_new_dummy(client->adapter, priv->cec_addr);
        if (!priv->cec)
                return -ENODEV;
 
-       priv->dpms = DRM_MODE_DPMS_OFF;
-
        mutex_init(&priv->mutex);       /* protect the page access */
        init_waitqueue_head(&priv->edid_delay_waitq);
        setup_timer(&priv->edid_delay_timer, tda998x_edid_delay_done,
@@ -1479,7 +1557,7 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
 
        /* initialize the optional IRQ */
        if (client->irq) {
-               int irqf_trigger;
+               unsigned long irq_flags;
 
                /* init read EDID waitqueue and HDP work */
                init_waitqueue_head(&priv->wq_edid);
@@ -1489,11 +1567,11 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
                reg_read(priv, REG_INT_FLAGS_1);
                reg_read(priv, REG_INT_FLAGS_2);
 
-               irqf_trigger =
+               irq_flags =
                        irqd_get_trigger_type(irq_get_irq_data(client->irq));
+               irq_flags |= IRQF_SHARED | IRQF_ONESHOT;
                ret = request_threaded_irq(client->irq, NULL,
-                                          tda998x_irq_thread,
-                                          irqf_trigger | IRQF_ONESHOT,
+                                          tda998x_irq_thread, irq_flags,
                                           "tda998x", priv);
                if (ret) {
                        dev_err(&client->dev,
@@ -1520,8 +1598,6 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
                priv->vip_cntrl_2 = video;
        }
 
-       mutex_init(&priv->audio_mutex); /* Protect access from audio thread */
-
        ret = tda998x_get_audio_ports(priv, np);
        if (ret)
                goto fail;
@@ -1568,44 +1644,25 @@ static const struct drm_encoder_funcs tda998x_encoder_funcs = {
        .destroy = tda998x_encoder_destroy,
 };
 
-static struct drm_encoder *
-tda998x_connector_best_encoder(struct drm_connector *connector)
+static void tda998x_set_config(struct tda998x_priv *priv,
+                              const struct tda998x_encoder_params *p)
 {
-       struct tda998x_priv *priv = conn_to_tda998x_priv(connector);
-
-       return &priv->encoder;
-}
-
-static
-const struct drm_connector_helper_funcs tda998x_connector_helper_funcs = {
-       .get_modes = tda998x_connector_get_modes,
-       .mode_valid = tda998x_connector_mode_valid,
-       .best_encoder = tda998x_connector_best_encoder,
-};
-
-static void tda998x_connector_destroy(struct drm_connector *connector)
-{
-       drm_connector_cleanup(connector);
-}
+       priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) |
+                           (p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) |
+                           VIP_CNTRL_0_SWAP_B(p->swap_b) |
+                           (p->mirr_b ? VIP_CNTRL_0_MIRR_B : 0);
+       priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(p->swap_c) |
+                           (p->mirr_c ? VIP_CNTRL_1_MIRR_C : 0) |
+                           VIP_CNTRL_1_SWAP_D(p->swap_d) |
+                           (p->mirr_d ? VIP_CNTRL_1_MIRR_D : 0);
+       priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(p->swap_e) |
+                           (p->mirr_e ? VIP_CNTRL_2_MIRR_E : 0) |
+                           VIP_CNTRL_2_SWAP_F(p->swap_f) |
+                           (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
 
-static int tda998x_connector_dpms(struct drm_connector *connector, int mode)
-{
-       if (drm_core_check_feature(connector->dev, DRIVER_ATOMIC))
-               return drm_atomic_helper_connector_dpms(connector, mode);
-       else
-               return drm_helper_connector_dpms(connector, mode);
+       priv->audio_params = p->audio_params;
 }
 
-static const struct drm_connector_funcs tda998x_connector_funcs = {
-       .dpms = tda998x_connector_dpms,
-       .reset = drm_atomic_helper_connector_reset,
-       .fill_modes = drm_helper_probe_single_connector_modes,
-       .detect = tda998x_connector_detect,
-       .destroy = tda998x_connector_destroy,
-       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
 static int tda998x_bind(struct device *dev, struct device *master, void *data)
 {
        struct tda998x_encoder_params *params = dev->platform_data;
@@ -1630,7 +1687,6 @@ static int tda998x_bind(struct device *dev, struct device *master, void *data)
                crtcs = 1 << 0;
        }
 
-       priv->connector.interlace_allowed = 1;
        priv->encoder.possible_crtcs = crtcs;
 
        ret = tda998x_create(client, priv);
@@ -1638,9 +1694,7 @@ static int tda998x_bind(struct device *dev, struct device *master, void *data)
                return ret;
 
        if (!dev->of_node && params)
-               tda998x_encoder_set_config(priv, params);
-
-       tda998x_encoder_set_polling(priv, &priv->connector);
+               tda998x_set_config(priv, params);
 
        drm_encoder_helper_add(&priv->encoder, &tda998x_encoder_helper_funcs);
        ret = drm_encoder_init(drm, &priv->encoder, &tda998x_encoder_funcs,
@@ -1648,16 +1702,10 @@ static int tda998x_bind(struct device *dev, struct device *master, void *data)
        if (ret)
                goto err_encoder;
 
-       drm_connector_helper_add(&priv->connector,
-                                &tda998x_connector_helper_funcs);
-       ret = drm_connector_init(drm, &priv->connector,
-                                &tda998x_connector_funcs,
-                                DRM_MODE_CONNECTOR_HDMIA);
+       ret = tda998x_connector_init(priv, drm);
        if (ret)
                goto err_connector;
 
-       drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder);
-
        return 0;
 
 err_connector:
@@ -1685,6 +1733,10 @@ static const struct component_ops tda998x_ops = {
 static int
 tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id)
 {
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_warn(&client->dev, "adapter does not support I2C\n");
+               return -EIO;
+       }
        return component_add(&client->dev, &tda998x_ops);
 }