drm/rockchip: hdmi: fix parse phy table error
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / rockchip / dw_hdmi-rockchip.c
index 52ac77e0130dc74648ff2dc4aac98950038ca78e..d23c1761d0eb406ecbd90ae4176905759599a0d9 100644 (file)
@@ -8,10 +8,14 @@
  */
 
 #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/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 "rockchip_drm_drv.h"
 #include "rockchip_drm_vop.h"
 
+#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)
 
-#define CLK_SLOP(clk)          ((clk) / 1000)
-#define CLK_PLUS_SLOP(clk)     ((clk) + CLK_SLOP(clk))
-
-static const int dw_hdmi_rates[] = {
-       25176471,       /* for 25.175 MHz, 0.006% off */
-       25200000,
-       27000000,
-       28320000,
-       30240000,
-       31500000,
-       32000000,
-       33750000,
-       36000000,
-       40000000,
-       49500000,
-       50000000,
-       54000000,
-       57290323,       /* for 57.284 MHz, .011 % off */
-       65000000,
-       68250000,
-       71000000,
-       72000000,
-       73250000,
-       74250000,
-       74437500,       /* for 74.44 MHz, .003% off */
-       75000000,
-       78750000,
-       78800000,
-       79500000,
-       83500000,
-       85500000,
-       88750000,
-       97750000,
-       101000000,
-       106500000,
-       108000000,
-       115500000,
-       118666667,      /* for 118.68 MHz, .011% off */
-       119000000,
-       121714286,      /* for 121.75 MHz, .029% off */
-       135000000,
-       136800000,      /* for 136.75 MHz, .037% off */
-       146250000,
-       148500000,
-       154000000,
-       162000000,
-       297000000,
-       594000000,
-};
+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
@@ -213,18 +259,44 @@ static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
        }
 };
 
-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},
        { 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;
+       int ret, val, phy_table_size;
+       u32 *phy_config;
 
        hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
        if (IS_ERR(hdmi->regmap)) {
@@ -252,12 +324,61 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
                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;
 }
 
@@ -265,9 +386,11 @@ static enum drm_mode_status
 dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
                            struct drm_display_mode *mode)
 {
-       int pclk = mode->clock * 1000;
-       int num_rates = ARRAY_SIZE(dw_hdmi_rates);
-       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;
 
        /*
         * Pixel clocks we support are always < 2GHz and so fit in an
@@ -276,16 +399,50 @@ dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
         */
        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;
 
-       for (i = 0; i < num_rates; i++) {
-               int slop = CLK_SLOP(pclk);
+       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 ((pclk >= dw_hdmi_rates[i] - slop) &&
-                   (pclk <= dw_hdmi_rates[i] + slop))
-                       return MODE_OK;
+       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 MODE_BAD;
+       return status;
 }
 
 static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = {
@@ -293,78 +450,46 @@ 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)
 {
        struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
-       int pclk = adj_mode->clock * 1000;
-       int best_diff = INT_MAX;
-       int best_clock = 0;
-       int slop;
-       int i;
-
-       /* Pick the best clock */
-       for (i = 0; i < ARRAY_SIZE(dw_hdmi_rates); i++) {
-               int diff = dw_hdmi_rates[i] - pclk;
-
-               if (diff < 0)
-                       diff = -diff;
-               if (diff < best_diff) {
-                       best_diff = diff;
-                       best_clock = dw_hdmi_rates[i];
 
-                       /* Bail early if we're exact */
-                       if (best_diff == 0)
-                               return true;
-               }
-       }
-
-       /* Double check that it's OK */
-       slop = CLK_SLOP(pclk);
-       if ((pclk >= best_clock - slop) && (pclk <= best_clock + slop)) {
-               adj_mode->clock = DIV_ROUND_UP(best_clock, 1000);
-               return true;
-       }
-
-       /* Shoudn't be here; we should have said rate wasn't valid */
-       dev_warn(hdmi->dev, "tried to set invalid rate %d\n", adj_mode->clock);
-       return false;
-}
-
-static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
-                                             struct drm_display_mode *mode,
-                                             struct drm_display_mode *adj_mode)
-{
-       struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
-
-       clk_set_rate(hdmi->vpll_clk, adj_mode->clock * 1000);
+       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;
 
+       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 = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
        if (mux)
@@ -391,21 +516,50 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
                                      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);
 
-       s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+       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 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,
@@ -415,6 +569,29 @@ static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = {
        .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,
@@ -424,9 +601,24 @@ static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = {
 };
 
 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 = &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
        },
@@ -438,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;
@@ -455,7 +647,7 @@ 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;
@@ -484,6 +676,19 @@ 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);
@@ -513,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,
        },
 };