drm/rockchip: hdmi: fix parse phy table error
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / rockchip / dw_hdmi-rockchip.c
index 65cc55a8647889d3a4cd2eb9b739a355a27d1270..d23c1761d0eb406ecbd90ae4176905759599a0d9 100644 (file)
@@ -8,11 +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)
 
+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
@@ -189,8 +284,8 @@ static int rockchip_hdmi_update_phy_table(struct rockchip_hdmi *hdmi,
                        rockchip_phy_config[i].mpixelclock = (u64)config[i * 4];
                else
                        rockchip_phy_config[i].mpixelclock = ~0UL;
-               rockchip_phy_config[i].term = (u16)config[i * 4 + 1];
-               rockchip_phy_config[i].sym_ctr = (u16)config[i * 4 + 2];
+               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];
        }
 
@@ -229,12 +324,39 @@ 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) {
@@ -244,7 +366,7 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
                        return -ENOMEM;
                }
                phy_table_size = val / 16;
-               of_property_read_u32_array(np, "rockchip,phy_table",
+               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);
@@ -277,6 +399,14 @@ 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;
 
        if (!encoder) {
                const struct drm_connector_helper_funcs *funcs;
@@ -321,6 +451,9 @@ static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = {
 
 static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
 {
+       struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+
+       clk_disable_unprepare(hdmi->dclk);
 }
 
 static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
@@ -338,18 +471,25 @@ static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
        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)
@@ -376,10 +516,22 @@ 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);
+
+       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_YUV8_1X24;
+               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;
@@ -395,6 +547,19 @@ static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_fun
        .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,
@@ -404,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,
@@ -413,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
        },
@@ -427,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;
@@ -444,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;
@@ -473,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);