drm/rockchip: hdmi: fix parse phy table error
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / rockchip / dw_hdmi-rockchip.c
index c65ce8cb30d38a93e9ca1f4d420dbacdbc2171f2..d23c1761d0eb406ecbd90ae4176905759599a0d9 100644 (file)
@@ -7,10 +7,16 @@
  * (at your option) any later version.
  */
 
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
-#include <linux/mfd/syscon.h>
+#include <linux/rockchip/cpu.h>
 #include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+#include <linux/phy/phy.h>
+
 #include <drm/drm_of.h>
 #include <drm/drmP.h>
 #include <drm/drm_crtc_helper.h>
 #include "rockchip_drm_drv.h"
 #include "rockchip_drm_vop.h"
 
-#define GRF_SOC_CON6                    0x025c
-#define HDMI_SEL_VOP_LIT                (1 << 4)
+#define RK3228_GRF_SOC_CON2            0x0408
+#define RK3228_DDC_MASK_EN             ((3 << 13) | (3 << (13 + 16)))
+#define RK3228_GRF_SOC_CON6            0x0418
+#define RK3228_IO_3V_DOMAIN            ((7 << 4) | (7 << (4 + 16)))
+
+#define RK3288_GRF_SOC_CON6            0x025C
+#define RK3288_HDMI_LCDC_SEL           BIT(4)
+#define RK3366_GRF_SOC_CON0            0x0400
+#define RK3366_HDMI_LCDC_SEL           BIT(1)
+#define RK3399_GRF_SOC_CON20           0x6250
+#define RK3399_HDMI_LCDC_SEL           BIT(6)
+
+#define RK3328_GRF_SOC_CON2            0x0408
+#define RK3328_DDC_MASK_EN             ((3 << 10) | (3 << (10 + 16)))
+#define RK3328_GRF_SOC_CON3            0x040c
+#define RK3328_IO_CTRL_BY_HDMI         (0xf0000000 | BIT(13) | BIT(12))
+#define RK3328_GRF_SOC_CON4            0x0410
+#define RK3328_IO_3V_DOMAIN            (7 << (9 + 16))
+#define RK3328_IO_5V_DOMAIN            ((7 << 9) | (3 << (9 + 16)))
+#define RK3328_HPD_3V                  (BIT(8 + 16) | BIT(13 + 16))
+
+#define HIWORD_UPDATE(val, mask)       (val | (mask) << 16)
 
 struct rockchip_hdmi {
        struct device *dev;
        struct regmap *regmap;
+       void __iomem *hdmiphy;
        struct drm_encoder encoder;
+       enum dw_hdmi_devtype dev_type;
+       struct clk *vpll_clk;
+       struct clk *grf_clk;
+       struct clk *hclk_vio;
+       struct clk *dclk;
+
+       struct phy *phy;
 };
 
 #define to_rockchip_hdmi(x)    container_of(x, struct rockchip_hdmi, x)
 
+static int
+inno_dw_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data,
+                     struct drm_display_mode *mode)
+{
+       struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+       return phy_power_on(hdmi->phy);
+}
+
+static void inno_dw_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data)
+{
+       struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+       phy_power_off(hdmi->phy);
+}
+
+static enum drm_connector_status
+inno_dw_hdmi_phy_read_hpd(struct dw_hdmi *dw_hdmi, void *data)
+{
+       struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+       enum drm_connector_status status;
+
+       status = dw_hdmi_phy_read_hpd(dw_hdmi, data);
+
+       if (hdmi->dev_type == RK3228_HDMI)
+               return status;
+
+       if (status == connector_status_connected)
+               regmap_write(hdmi->regmap,
+                            RK3328_GRF_SOC_CON4,
+                            RK3328_IO_5V_DOMAIN);
+       else
+               regmap_write(hdmi->regmap,
+                            RK3328_GRF_SOC_CON4,
+                            RK3328_IO_3V_DOMAIN);
+       return status;
+}
+
+static int inno_dw_hdmi_init(struct rockchip_hdmi *hdmi)
+{
+       int ret;
+
+       ret = clk_prepare_enable(hdmi->grf_clk);
+       if (ret < 0) {
+               dev_err(hdmi->dev, "failed to enable grfclk %d\n", ret);
+               return -EPROBE_DEFER;
+       }
+       if (hdmi->dev_type == RK3328_HDMI) {
+               /* Map HPD pin to 3V io */
+               regmap_write(hdmi->regmap,
+                            RK3328_GRF_SOC_CON4,
+                            RK3328_IO_3V_DOMAIN |
+                            RK3328_HPD_3V);
+               /* Map ddc pin to 5V io */
+               regmap_write(hdmi->regmap,
+                            RK3328_GRF_SOC_CON3,
+                            RK3328_IO_CTRL_BY_HDMI);
+               regmap_write(hdmi->regmap,
+                            RK3328_GRF_SOC_CON2,
+                            RK3328_DDC_MASK_EN |
+                            BIT(18));
+       } else {
+               regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON2,
+                            RK3228_DDC_MASK_EN);
+               regmap_write(hdmi->regmap, RK3228_GRF_SOC_CON6,
+                            RK3228_IO_3V_DOMAIN);
+       }
+       clk_disable_unprepare(hdmi->grf_clk);
+       return 0;
+}
+
+/*
+ * There are some rates that would be ranged for better clock jitter at
+ * Chrome OS tree, like 25.175Mhz would range to 25.170732Mhz. But due
+ * to the clock is aglined to KHz in struct drm_display_mode, this would
+ * bring some inaccurate error if we still run the compute_n math, so
+ * let's just code an const table for it until we can actually get the
+ * right clock rate.
+ */
+static const struct dw_hdmi_audio_tmds_n rockchip_werid_tmds_n_table[] = {
+       /* 25176471 for 25.175 MHz = 428000000 / 17. */
+       { .tmds = 25177000, .n_32k = 4352, .n_44k1 = 14994, .n_48k = 6528, },
+       /* 57290323 for 57.284 MHz */
+       { .tmds = 57291000, .n_32k = 3968, .n_44k1 = 4557, .n_48k = 5952, },
+       /* 74437500 for 74.44 MHz = 297750000 / 4 */
+       { .tmds = 74438000, .n_32k = 8192, .n_44k1 = 18816, .n_48k = 4096, },
+       /* 118666667 for 118.68 MHz */
+       { .tmds = 118667000, .n_32k = 4224, .n_44k1 = 5292, .n_48k = 6336, },
+       /* 121714286 for 121.75 MHz */
+       { .tmds = 121715000, .n_32k = 4480, .n_44k1 = 6174, .n_48k = 6272, },
+       /* 136800000 for 136.75 MHz */
+       { .tmds = 136800000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
+       /* End of table */
+       { .tmds = 0,         .n_32k = 0,    .n_44k1 = 0,    .n_48k = 0, },
+};
+
 static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
        {
-               27000000, {
-                       { 0x00b3, 0x0000},
-                       { 0x2153, 0x0000},
-                       { 0x40f3, 0x0000}
+               30666000, {
+                       { 0x00b3, 0x0000 },
+                       { 0x2153, 0x0000 },
+                       { 0x40f3, 0x0000 },
+               },
+       },  {
+               36800000, {
+                       { 0x00b3, 0x0000 },
+                       { 0x2153, 0x0000 },
+                       { 0x40a2, 0x0001 },
                },
-       }, {
-               36000000, {
-                       { 0x00b3, 0x0000},
-                       { 0x2153, 0x0000},
-                       { 0x40f3, 0x0000}
+       },  {
+               46000000, {
+                       { 0x00b3, 0x0000 },
+                       { 0x2142, 0x0001 },
+                       { 0x40a2, 0x0001 },
                },
-       }, {
-               40000000, {
-                       { 0x00b3, 0x0000},
-                       { 0x2153, 0x0000},
-                       { 0x40f3, 0x0000}
+       },  {
+               61333000, {
+                       { 0x0072, 0x0001 },
+                       { 0x2142, 0x0001 },
+                       { 0x40a2, 0x0001 },
                },
-       }, {
-               54000000, {
-                       { 0x0072, 0x0001},
-                       { 0x2142, 0x0001},
-                       { 0x40a2, 0x0001},
+       },  {
+               73600000, {
+                       { 0x0072, 0x0001 },
+                       { 0x2142, 0x0001 },
+                       { 0x4061, 0x0002 },
                },
-       }, {
-               65000000, {
-                       { 0x0072, 0x0001},
-                       { 0x2142, 0x0001},
-                       { 0x40a2, 0x0001},
+       },  {
+               92000000, {
+                       { 0x0072, 0x0001 },
+                       { 0x2145, 0x0002 },
+                       { 0x4061, 0x0002 },
                },
-       }, {
-               66000000, {
-                       { 0x013e, 0x0003},
-                       { 0x217e, 0x0002},
-                       { 0x4061, 0x0002}
+       },  {
+               122666000, {
+                       { 0x0051, 0x0002 },
+                       { 0x2145, 0x0002 },
+                       { 0x4061, 0x0002 },
                },
-       }, {
-               74250000, {
-                       { 0x0072, 0x0001},
-                       { 0x2145, 0x0002},
-                       { 0x4061, 0x0002}
+       },  {
+               147200000, {
+                       { 0x0051, 0x0002 },
+                       { 0x2145, 0x0002 },
+                       { 0x4064, 0x0003 },
                },
-       }, {
-               83500000, {
-                       { 0x0072, 0x0001},
+       },  {
+               184000000, {
+                       { 0x0051, 0x0002 },
+                       { 0x214c, 0x0003 },
+                       { 0x4064, 0x0003 },
                },
-       }, {
-               108000000, {
-                       { 0x0051, 0x0002},
-                       { 0x2145, 0x0002},
-                       { 0x4061, 0x0002}
+       },  {
+               226666000, {
+                       { 0x0040, 0x0003 },
+                       { 0x214c, 0x0003 },
+                       { 0x4064, 0x0003 },
                },
-       }, {
-               106500000, {
-                       { 0x0051, 0x0002},
-                       { 0x2145, 0x0002},
-                       { 0x4061, 0x0002}
+       },  {
+               272000000, {
+                       { 0x0040, 0x0003 },
+                       { 0x214c, 0x0003 },
+                       { 0x5a64, 0x0003 },
                },
-       }, {
-               146250000, {
-                       { 0x0051, 0x0002},
-                       { 0x2145, 0x0002},
-                       { 0x4061, 0x0002}
+       },  {
+               340000000, {
+                       { 0x0040, 0x0003 },
+                       { 0x3b4c, 0x0003 },
+                       { 0x5a64, 0x0003 },
                },
-       }, {
-               148500000, {
-                       { 0x0051, 0x0003},
-                       { 0x214c, 0x0003},
-                       { 0x4064, 0x0003}
+       },  {
+               600000000, {
+                       { 0x1a40, 0x0003 },
+                       { 0x3b4c, 0x0003 },
+                       { 0x5a64, 0x0003 },
                },
-       }, {
+       },  {
                ~0UL, {
-                       { 0x00a0, 0x000a },
-                       { 0x2001, 0x000f },
-                       { 0x4002, 0x000f },
+                       { 0x0000, 0x0000 },
+                       { 0x0000, 0x0000 },
+                       { 0x0000, 0x0000 },
                },
        }
 };
@@ -115,35 +253,50 @@ static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
 static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
        /*      pixelclk    bpp8    bpp10   bpp12 */
        {
-               40000000,  { 0x0018, 0x0018, 0x0018 },
-       }, {
-               65000000,  { 0x0028, 0x0028, 0x0028 },
-       }, {
-               66000000,  { 0x0038, 0x0038, 0x0038 },
-       }, {
-               74250000,  { 0x0028, 0x0038, 0x0038 },
-       }, {
-               83500000,  { 0x0028, 0x0038, 0x0038 },
-       }, {
-               146250000, { 0x0038, 0x0038, 0x0038 },
-       }, {
-               148500000, { 0x0000, 0x0038, 0x0038 },
-       }, {
+               600000000, { 0x0000, 0x0000, 0x0000 },
+       },  {
                ~0UL,      { 0x0000, 0x0000, 0x0000},
        }
 };
 
-static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
+static struct dw_hdmi_phy_config rockchip_phy_config[] = {
        /*pixelclk   symbol   term   vlev*/
        { 74250000,  0x8009, 0x0004, 0x0272},
-       { 148500000, 0x802b, 0x0004, 0x028d},
+       { 165000000, 0x802b, 0x0004, 0x0209},
        { 297000000, 0x8039, 0x0005, 0x028d},
+       { 594000000, 0x8039, 0x0000, 0x019d},
        { ~0UL,      0x0000, 0x0000, 0x0000}
 };
 
+static int rockchip_hdmi_update_phy_table(struct rockchip_hdmi *hdmi,
+                                         u32 *config,
+                                         int phy_table_size)
+{
+       int i;
+
+       if (phy_table_size > ARRAY_SIZE(rockchip_phy_config)) {
+               dev_err(hdmi->dev, "phy table array number is out of range\n");
+               return -E2BIG;
+       }
+
+       for (i = 0; i < phy_table_size; i++) {
+               if (config[i * 4] != 0)
+                       rockchip_phy_config[i].mpixelclock = (u64)config[i * 4];
+               else
+                       rockchip_phy_config[i].mpixelclock = ~0UL;
+               rockchip_phy_config[i].sym_ctr = (u16)config[i * 4 + 1];
+               rockchip_phy_config[i].term = (u16)config[i * 4 + 2];
+               rockchip_phy_config[i].vlev_ctr = (u16)config[i * 4 + 3];
+       }
+
+       return 0;
+}
+
 static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
 {
        struct device_node *np = hdmi->dev->of_node;
+       int ret, val, phy_table_size;
+       u32 *phy_config;
 
        hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
        if (IS_ERR(hdmi->regmap)) {
@@ -151,6 +304,81 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
                return PTR_ERR(hdmi->regmap);
        }
 
+       hdmi->vpll_clk = devm_clk_get(hdmi->dev, "vpll");
+       if (PTR_ERR(hdmi->vpll_clk) == -ENOENT) {
+               hdmi->vpll_clk = NULL;
+       } else if (PTR_ERR(hdmi->vpll_clk) == -EPROBE_DEFER) {
+               return -EPROBE_DEFER;
+       } else if (IS_ERR(hdmi->vpll_clk)) {
+               dev_err(hdmi->dev, "failed to get grf clock\n");
+               return PTR_ERR(hdmi->vpll_clk);
+       }
+
+       hdmi->grf_clk = devm_clk_get(hdmi->dev, "grf");
+       if (PTR_ERR(hdmi->grf_clk) == -ENOENT) {
+               hdmi->grf_clk = NULL;
+       } else if (PTR_ERR(hdmi->grf_clk) == -EPROBE_DEFER) {
+               return -EPROBE_DEFER;
+       } else if (IS_ERR(hdmi->grf_clk)) {
+               dev_err(hdmi->dev, "failed to get grf clock\n");
+               return PTR_ERR(hdmi->grf_clk);
+       }
+
+       hdmi->hclk_vio = devm_clk_get(hdmi->dev, "hclk_vio");
+       if (PTR_ERR(hdmi->hclk_vio) == -ENOENT) {
+               hdmi->hclk_vio = NULL;
+       } else if (PTR_ERR(hdmi->hclk_vio) == -EPROBE_DEFER) {
+               return -EPROBE_DEFER;
+       } else if (IS_ERR(hdmi->hclk_vio)) {
+               dev_err(hdmi->dev, "failed to get hclk_vio clock\n");
+               return PTR_ERR(hdmi->hclk_vio);
+       }
+
+       hdmi->dclk = devm_clk_get(hdmi->dev, "dclk");
+       if (PTR_ERR(hdmi->dclk) == -ENOENT) {
+               hdmi->dclk = NULL;
+       } else if (PTR_ERR(hdmi->dclk) == -EPROBE_DEFER) {
+               return -EPROBE_DEFER;
+       } else if (IS_ERR(hdmi->dclk)) {
+               dev_err(hdmi->dev, "failed to get dclk\n");
+               return PTR_ERR(hdmi->dclk);
+       }
+
+       ret = clk_prepare_enable(hdmi->vpll_clk);
+       if (ret) {
+               dev_err(hdmi->dev, "Failed to enable HDMI vpll: %d\n", ret);
+               return ret;
+       }
+
+       ret = clk_prepare_enable(hdmi->hclk_vio);
+       if (ret) {
+               dev_err(hdmi->dev, "Failed to eanble HDMI hclk_vio: %d\n",
+                       ret);
+               return ret;
+       }
+
+       if (of_get_property(np, "rockchip,phy-table", &val)) {
+               phy_config = kmalloc(val, GFP_KERNEL);
+               if (!phy_config) {
+                       /* use default table when kmalloc failed. */
+                       dev_err(hdmi->dev, "kmalloc phy table failed\n");
+
+                       return -ENOMEM;
+               }
+               phy_table_size = val / 16;
+               of_property_read_u32_array(np, "rockchip,phy-table",
+                                          phy_config, val / sizeof(u32));
+               ret = rockchip_hdmi_update_phy_table(hdmi, phy_config,
+                                                    phy_table_size);
+               if (ret) {
+                       kfree(phy_config);
+                       return ret;
+               }
+               kfree(phy_config);
+       } else {
+               dev_dbg(hdmi->dev, "use default hdmi phy table\n");
+       }
+
        return 0;
 }
 
@@ -158,19 +386,63 @@ static enum drm_mode_status
 dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
                            struct drm_display_mode *mode)
 {
-       const struct dw_hdmi_mpll_config *mpll_cfg = rockchip_mpll_cfg;
-       int pclk = mode->clock * 1000;
-       bool valid = false;
-       int i;
+       struct drm_encoder *encoder = connector->encoder;
+       enum drm_mode_status status = MODE_OK;
+       struct drm_device *dev = connector->dev;
+       struct rockchip_drm_private *priv = dev->dev_private;
+       struct drm_crtc *crtc;
 
-       for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) {
-               if (pclk == mpll_cfg[i].mpixelclock) {
-                       valid = true;
-                       break;
-               }
+       /*
+        * Pixel clocks we support are always < 2GHz and so fit in an
+        * int.  We should make sure source rate does too so we don't get
+        * overflow when we multiply by 1000.
+        */
+       if (mode->clock > INT_MAX / 1000)
+               return MODE_BAD;
+       /*
+        * If sink max TMDS clock < 340MHz, we should check the mode pixel
+        * clock > 340MHz is YCbCr420 or not.
+        */
+       if (mode->clock > 340000 &&
+           connector->display_info.max_tmds_clock < 340000 &&
+           !(mode->flags & DRM_MODE_FLAG_420_MASK))
+               return MODE_BAD;
+
+       if (!encoder) {
+               const struct drm_connector_helper_funcs *funcs;
+
+               funcs = connector->helper_private;
+               if (funcs->atomic_best_encoder)
+                       encoder = funcs->atomic_best_encoder(connector,
+                                                            connector->state);
+               else
+                       encoder = funcs->best_encoder(connector);
+       }
+
+       if (!encoder || !encoder->possible_crtcs)
+               return MODE_BAD;
+       /*
+        * ensure all drm display mode can work, if someone want support more
+        * resolutions, please limit the possible_crtc, only connect to
+        * needed crtc.
+        */
+       drm_for_each_crtc(crtc, connector->dev) {
+               int pipe = drm_crtc_index(crtc);
+               const struct rockchip_crtc_funcs *funcs =
+                                               priv->crtc_funcs[pipe];
+
+               if (!(encoder->possible_crtcs & drm_crtc_mask(crtc)))
+                       continue;
+               if (!funcs || !funcs->mode_valid)
+                       continue;
+
+               status = funcs->mode_valid(crtc, mode,
+                                          DRM_MODE_CONNECTOR_HDMIA);
+               if (status != MODE_OK)
+                       return status;
        }
 
-       return (valid) ? MODE_OK : MODE_BAD;
+       return status;
 }
 
 static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = {
@@ -179,60 +451,176 @@ static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = {
 
 static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
 {
-}
-
-static bool
-dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder,
-                                   const struct drm_display_mode *mode,
-                                   struct drm_display_mode *adj_mode)
-{
-       return true;
-}
+       struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
 
-static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
-                                             struct drm_display_mode *mode,
-                                             struct drm_display_mode *adj_mode)
-{
+       clk_disable_unprepare(hdmi->dclk);
 }
 
 static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
 {
        struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+       struct drm_crtc *crtc = encoder->crtc;
+       u32 lcdsel_grf_reg, lcdsel_mask;
        u32 val;
        int mux;
+       int ret;
 
-       rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_HDMIA,
-                                     ROCKCHIP_OUT_MODE_AAAA);
+       if (WARN_ON(!crtc || !crtc->state))
+               return;
+
+       clk_set_rate(hdmi->vpll_clk,
+                    crtc->state->adjusted_mode.crtc_clock * 1000);
+
+       clk_set_rate(hdmi->dclk, crtc->state->adjusted_mode.crtc_clock * 1000);
+       clk_prepare_enable(hdmi->dclk);
+
+       switch (hdmi->dev_type) {
+       case RK3288_HDMI:
+               lcdsel_grf_reg = RK3288_GRF_SOC_CON6;
+               lcdsel_mask = RK3288_HDMI_LCDC_SEL;
+               break;
+       case RK3366_HDMI:
+               lcdsel_grf_reg = RK3366_GRF_SOC_CON0;
+               lcdsel_mask = RK3366_HDMI_LCDC_SEL;
+               break;
+       case RK3399_HDMI:
+               lcdsel_grf_reg = RK3399_GRF_SOC_CON20;
+               lcdsel_mask = RK3399_HDMI_LCDC_SEL;
+               break;
+       default:
+               return;
+       }
 
-       mux = rockchip_drm_encoder_get_mux_id(hdmi->dev->of_node, encoder);
+       mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
        if (mux)
-               val = HDMI_SEL_VOP_LIT | (HDMI_SEL_VOP_LIT << 16);
+               val = HIWORD_UPDATE(lcdsel_mask, lcdsel_mask);
        else
-               val = HDMI_SEL_VOP_LIT << 16;
+               val = HIWORD_UPDATE(0, lcdsel_mask);
+
+       ret = clk_prepare_enable(hdmi->grf_clk);
+       if (ret < 0) {
+               dev_err(hdmi->dev, "failed to enable grfclk %d\n", ret);
+               return;
+       }
 
-       regmap_write(hdmi->regmap, GRF_SOC_CON6, val);
+       regmap_write(hdmi->regmap, lcdsel_grf_reg, val);
        dev_dbg(hdmi->dev, "vop %s output to hdmi\n",
                (mux) ? "LIT" : "BIG");
+
+       clk_disable_unprepare(hdmi->grf_clk);
+}
+
+static int
+dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
+                                     struct drm_crtc_state *crtc_state,
+                                     struct drm_connector_state *conn_state)
+{
+       struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+       struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+
+       if (hdmi->phy) {
+               if (drm_match_cea_mode(&crtc_state->mode) > 94 &&
+                   crtc_state->mode.crtc_clock > 340000 &&
+                   !(crtc_state->mode.flags & DRM_MODE_FLAG_420_MASK)) {
+                       crtc_state->mode.flags |= DRM_MODE_FLAG_420;
+                       phy_set_bus_width(hdmi->phy, 4);
+               } else {
+                       phy_set_bus_width(hdmi->phy, 8);
+               }
+       }
+
+       if (crtc_state->mode.flags & DRM_MODE_FLAG_420_MASK) {
+               s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
+               s->bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24;
+       } else {
+               s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+               s->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+       }
+       s->output_type = DRM_MODE_CONNECTOR_HDMIA;
+
+       return 0;
 }
 
 static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
-       .mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup,
-       .mode_set   = dw_hdmi_rockchip_encoder_mode_set,
        .enable     = dw_hdmi_rockchip_encoder_enable,
        .disable    = dw_hdmi_rockchip_encoder_disable,
+       .atomic_check = dw_hdmi_rockchip_encoder_atomic_check,
+};
+
+static const struct dw_hdmi_phy_ops inno_dw_hdmi_phy_ops = {
+       .init           = inno_dw_hdmi_phy_init,
+       .disable        = inno_dw_hdmi_phy_disable,
+       .read_hpd       = inno_dw_hdmi_phy_read_hpd,
 };
 
-static const struct dw_hdmi_plat_data rockchip_hdmi_drv_data = {
+static const struct dw_hdmi_plat_data rk3228_hdmi_drv_data = {
+       .mode_valid = dw_hdmi_rockchip_mode_valid,
+       .phy_ops    = &inno_dw_hdmi_phy_ops,
+       .phy_name   = "inno_dw_hdmi_phy",
+       .dev_type   = RK3228_HDMI,
+};
+
+static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = {
        .mode_valid = dw_hdmi_rockchip_mode_valid,
        .mpll_cfg   = rockchip_mpll_cfg,
        .cur_ctr    = rockchip_cur_ctr,
        .phy_config = rockchip_phy_config,
        .dev_type   = RK3288_HDMI,
+       .tmds_n_table = rockchip_werid_tmds_n_table,
+};
+
+static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = {
+       .mode_valid = dw_hdmi_rockchip_mode_valid,
+       .phy_ops    = &inno_dw_hdmi_phy_ops,
+       .phy_name   = "inno_dw_hdmi_phy2",
+       .dev_type   = RK3328_HDMI,
+};
+
+static const struct dw_hdmi_plat_data rk3366_hdmi_drv_data = {
+       .mode_valid = dw_hdmi_rockchip_mode_valid,
+       .mpll_cfg   = rockchip_mpll_cfg,
+       .cur_ctr    = rockchip_cur_ctr,
+       .phy_config = rockchip_phy_config,
+       .dev_type   = RK3366_HDMI,
+};
+
+static const struct dw_hdmi_plat_data rk3368_hdmi_drv_data = {
+       .mode_valid = dw_hdmi_rockchip_mode_valid,
+       .mpll_cfg   = rockchip_mpll_cfg,
+       .cur_ctr    = rockchip_cur_ctr,
+       .phy_config = rockchip_phy_config,
+       .dev_type   = RK3368_HDMI,
+};
+
+static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = {
+       .mode_valid = dw_hdmi_rockchip_mode_valid,
+       .mpll_cfg   = rockchip_mpll_cfg,
+       .cur_ctr    = rockchip_cur_ctr,
+       .phy_config = rockchip_phy_config,
+       .dev_type   = RK3399_HDMI,
 };
 
 static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
+       { .compatible = "rockchip,rk3228-dw-hdmi",
+         .data = &rk3228_hdmi_drv_data
+       },
        { .compatible = "rockchip,rk3288-dw-hdmi",
-         .data = &rockchip_hdmi_drv_data
+         .data = &rk3288_hdmi_drv_data
+       },
+       {
+         .compatible = "rockchip,rk3328-dw-hdmi",
+         .data = &rk3328_hdmi_drv_data
+       },
+       {
+        .compatible = "rockchip,rk3366-dw-hdmi",
+        .data = &rk3366_hdmi_drv_data
+       },
+       {
+        .compatible = "rockchip,rk3368-dw-hdmi",
+        .data = &rk3368_hdmi_drv_data
+       },
+       { .compatible = "rockchip,rk3399-dw-hdmi",
+         .data = &rk3399_hdmi_drv_data
        },
        {},
 };
@@ -242,7 +630,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
                                 void *data)
 {
        struct platform_device *pdev = to_platform_device(dev);
-       const struct dw_hdmi_plat_data *plat_data;
+       struct dw_hdmi_plat_data *plat_data;
        const struct of_device_id *match;
        struct drm_device *drm = data;
        struct drm_encoder *encoder;
@@ -259,8 +647,9 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
                return -ENOMEM;
 
        match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
-       plat_data = match->data;
+       plat_data = (struct dw_hdmi_plat_data *)match->data;
        hdmi->dev = &pdev->dev;
+       hdmi->dev_type = plat_data->dev_type;
        encoder = &hdmi->encoder;
 
        irq = platform_get_irq(pdev, 0);
@@ -271,8 +660,6 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
        if (!iores)
                return -ENXIO;
 
-       platform_set_drvdata(pdev, hdmi);
-
        encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
        /*
         * If we failed to find the CRTC(s) which this encoder is
@@ -289,11 +676,33 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
                return ret;
        }
 
+       if (hdmi->dev_type == RK3328_HDMI || hdmi->dev_type == RK3228_HDMI) {
+               hdmi->phy = devm_phy_get(dev, "hdmi_phy");
+               if (IS_ERR(hdmi->phy)) {
+                       ret = PTR_ERR(hdmi->phy);
+                       dev_err(dev, "failed to get phy: %d\n", ret);
+                       return ret;
+               }
+               plat_data->phy_data = hdmi;
+               ret = inno_dw_hdmi_init(hdmi);
+               if (ret < 0)
+                       return ret;
+       }
+
        drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
        drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs,
                         DRM_MODE_ENCODER_TMDS, NULL);
 
-       return dw_hdmi_bind(dev, master, data, encoder, iores, irq, plat_data);
+       ret = dw_hdmi_bind(dev, master, data, encoder, iores, irq, plat_data);
+
+       /*
+        * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
+        * which would have called the encoder cleanup.  Do it manually.
+        */
+       if (ret)
+               drm_encoder_cleanup(encoder);
+
+       return ret;
 }
 
 static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
@@ -309,22 +718,48 @@ static const struct component_ops dw_hdmi_rockchip_ops = {
 
 static int dw_hdmi_rockchip_probe(struct platform_device *pdev)
 {
+       pm_runtime_enable(&pdev->dev);
+       pm_runtime_get_sync(&pdev->dev);
+
        return component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
 }
 
 static int dw_hdmi_rockchip_remove(struct platform_device *pdev)
 {
        component_del(&pdev->dev, &dw_hdmi_rockchip_ops);
+       pm_runtime_disable(&pdev->dev);
 
        return 0;
 }
 
+static int dw_hdmi_rockchip_suspend(struct device *dev)
+{
+       dw_hdmi_suspend(dev);
+       pm_runtime_put_sync(dev);
+
+       return 0;
+}
+
+static int dw_hdmi_rockchip_resume(struct device *dev)
+{
+       pm_runtime_get_sync(dev);
+       dw_hdmi_resume(dev);
+
+       return  0;
+}
+
+static const struct dev_pm_ops dw_hdmi_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_rockchip_suspend,
+                               dw_hdmi_rockchip_resume)
+};
+
 static struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
        .probe  = dw_hdmi_rockchip_probe,
        .remove = dw_hdmi_rockchip_remove,
        .driver = {
                .name = "dwhdmi-rockchip",
                .of_match_table = dw_hdmi_rockchip_dt_ids,
+               .pm = &dw_hdmi_pm_ops,
        },
 };