X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=drivers%2Fgpu%2Fdrm%2Frockchip%2Fdw_hdmi-rockchip.c;h=02e424b6f3ad06c055bbc44ed88fa6f9249321af;hb=7b2f394bb8ae3881aed47728d8d74a9e094badd8;hp=c65ce8cb30d38a93e9ca1f4d420dbacdbc2171f2;hpb=7a8bd6a860e186a7e42e19bbfc8cf124c355db11;p=firefly-linux-kernel-4.4.55.git diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index c65ce8cb30d3..02e424b6f3ad 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -7,10 +7,12 @@ * (at your option) any later version. */ +#include +#include #include #include -#include #include + #include #include #include @@ -21,93 +23,183 @@ #include "rockchip_drm_drv.h" #include "rockchip_drm_vop.h" -#define GRF_SOC_CON6 0x025c -#define HDMI_SEL_VOP_LIT (1 << 4) +#define RK3288_GRF_SOC_CON6 0x025C +#define RK3288_HDMI_LCDC_SEL BIT(4) +#define RK3399_GRF_SOC_CON20 0x6250 +#define RK3399_HDMI_LCDC_SEL BIT(6) + +#define HIWORD_UPDATE(val, mask) (val | (mask) << 16) struct rockchip_hdmi { struct device *dev; struct regmap *regmap; struct drm_encoder encoder; + enum dw_hdmi_devtype dev_type; + struct clk *vpll_clk; + struct clk *grf_clk; }; #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, +}; + +/* + * 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 }, }, - }, { - 36000000, { - { 0x00b3, 0x0000}, - { 0x2153, 0x0000}, - { 0x40f3, 0x0000} + }, { + 36800000, { + { 0x00b3, 0x0000 }, + { 0x2153, 0x0000 }, + { 0x40a2, 0x0001 }, }, - }, { - 40000000, { - { 0x00b3, 0x0000}, - { 0x2153, 0x0000}, - { 0x40f3, 0x0000} + }, { + 46000000, { + { 0x00b3, 0x0000 }, + { 0x2142, 0x0001 }, + { 0x40a2, 0x0001 }, }, - }, { - 54000000, { - { 0x0072, 0x0001}, - { 0x2142, 0x0001}, - { 0x40a2, 0x0001}, + }, { + 61333000, { + { 0x0072, 0x0001 }, + { 0x2142, 0x0001 }, + { 0x40a2, 0x0001 }, }, - }, { - 65000000, { - { 0x0072, 0x0001}, - { 0x2142, 0x0001}, - { 0x40a2, 0x0001}, + }, { + 73600000, { + { 0x0072, 0x0001 }, + { 0x2142, 0x0001 }, + { 0x4061, 0x0002 }, }, - }, { - 66000000, { - { 0x013e, 0x0003}, - { 0x217e, 0x0002}, - { 0x4061, 0x0002} + }, { + 92000000, { + { 0x0072, 0x0001 }, + { 0x2145, 0x0002 }, + { 0x4061, 0x0002 }, }, - }, { - 74250000, { - { 0x0072, 0x0001}, - { 0x2145, 0x0002}, - { 0x4061, 0x0002} + }, { + 122666000, { + { 0x0051, 0x0002 }, + { 0x2145, 0x0002 }, + { 0x4061, 0x0002 }, }, - }, { - 83500000, { - { 0x0072, 0x0001}, + }, { + 147200000, { + { 0x0051, 0x0002 }, + { 0x2145, 0x0002 }, + { 0x4064, 0x0003 }, }, - }, { - 108000000, { - { 0x0051, 0x0002}, - { 0x2145, 0x0002}, - { 0x4061, 0x0002} + }, { + 184000000, { + { 0x0051, 0x0002 }, + { 0x214c, 0x0003 }, + { 0x4064, 0x0003 }, }, - }, { - 106500000, { - { 0x0051, 0x0002}, - { 0x2145, 0x0002}, - { 0x4061, 0x0002} + }, { + 226666000, { + { 0x0040, 0x0003 }, + { 0x214c, 0x0003 }, + { 0x4064, 0x0003 }, }, - }, { - 146250000, { - { 0x0051, 0x0002}, - { 0x2145, 0x0002}, - { 0x4061, 0x0002} + }, { + 272000000, { + { 0x0040, 0x0003 }, + { 0x214c, 0x0003 }, + { 0x5a64, 0x0003 }, }, - }, { - 148500000, { - { 0x0051, 0x0003}, - { 0x214c, 0x0003}, - { 0x4064, 0x0003} + }, { + 340000000, { + { 0x0040, 0x0003 }, + { 0x3b4c, 0x0003 }, + { 0x5a64, 0x0003 }, }, - }, { + }, { + 600000000, { + { 0x1a40, 0x0003 }, + { 0x3b4c, 0x0003 }, + { 0x5a64, 0x0003 }, + }, + }, { ~0UL, { - { 0x00a0, 0x000a }, - { 0x2001, 0x000f }, - { 0x4002, 0x000f }, + { 0x0000, 0x0000 }, + { 0x0000, 0x0000 }, + { 0x0000, 0x0000 }, }, } }; @@ -115,20 +207,8 @@ 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}, } }; @@ -136,7 +216,7 @@ static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = { static const 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}, { ~0UL, 0x0000, 0x0000, 0x0000} }; @@ -144,6 +224,7 @@ static const struct dw_hdmi_phy_config rockchip_phy_config[] = { static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) { struct device_node *np = hdmi->dev->of_node; + int ret; hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); if (IS_ERR(hdmi->regmap)) { @@ -151,6 +232,32 @@ 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); + } + + ret = clk_prepare_enable(hdmi->vpll_clk); + if (ret) { + dev_err(hdmi->dev, "Failed to enable HDMI vpll: %d\n", ret); + return ret; + } + return 0; } @@ -158,19 +265,27 @@ 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 num_rates = ARRAY_SIZE(dw_hdmi_rates); int i; - 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; + + for (i = 0; i < num_rates; i++) { + int slop = CLK_SLOP(pclk); + + if ((pclk >= dw_hdmi_rates[i] - slop) && + (pclk <= dw_hdmi_rates[i] + slop)) + return MODE_OK; } - return (valid) ? MODE_OK : MODE_BAD; + return MODE_BAD; } static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = { @@ -186,33 +301,102 @@ 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); + 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); } static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); + 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); - - mux = rockchip_drm_encoder_get_mux_id(hdmi->dev->of_node, encoder); + switch (hdmi->dev_type) { + case RK3288_HDMI: + lcdsel_grf_reg = RK3288_GRF_SOC_CON6; + lcdsel_mask = RK3288_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) - 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); + + s->output_mode = ROCKCHIP_OUT_MODE_AAAA; + s->output_type = DRM_MODE_CONNECTOR_HDMIA; + s->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + return 0; } static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = { @@ -220,19 +404,32 @@ static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_fun .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_plat_data rockchip_hdmi_drv_data = { +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 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,rk3288-dw-hdmi", - .data = &rockchip_hdmi_drv_data + .data = &rk3288_hdmi_drv_data + }, + { .compatible = "rockchip,rk3399-dw-hdmi", + .data = &rk3399_hdmi_drv_data }, {}, }; @@ -261,6 +458,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node); 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 +469,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 @@ -293,7 +489,16 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, 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,