diff options
Diffstat (limited to 'drivers/gpu/drm/meson/meson_dw_hdmi.c')
-rw-r--r-- | drivers/gpu/drm/meson/meson_dw_hdmi.c | 180 |
1 files changed, 141 insertions, 39 deletions
diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index 3bb7ffe5fc39..e8c94915a4fc 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -16,6 +16,7 @@ #include <drm/bridge/dw_hdmi.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> #include <drm/drm_device.h> #include <drm/drm_edid.h> #include <drm/drm_probe_helper.h> @@ -135,6 +136,7 @@ struct meson_dw_hdmi_data { struct meson_dw_hdmi { struct drm_encoder encoder; + struct drm_bridge bridge; struct dw_hdmi_plat_data dw_plat_data; struct meson_drm *priv; struct device *dev; @@ -148,9 +150,12 @@ struct meson_dw_hdmi { struct regulator *hdmi_supply; u32 irq_stat; struct dw_hdmi *hdmi; + unsigned long output_bus_fmt; }; #define encoder_to_meson_dw_hdmi(x) \ container_of(x, struct meson_dw_hdmi, encoder) +#define bridge_to_meson_dw_hdmi(x) \ + container_of(x, struct meson_dw_hdmi, bridge) static inline int dw_hdmi_is_compatible(struct meson_dw_hdmi *dw_hdmi, const char *compat) @@ -297,6 +302,10 @@ static void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi, struct meson_drm *priv = dw_hdmi->priv; unsigned int pixel_clock = mode->clock; + /* For 420, pixel clock is half unlike venc clock */ + if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) + pixel_clock /= 2; + if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi")) { if (pixel_clock >= 371250) { @@ -368,29 +377,40 @@ static inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi) } static void dw_hdmi_set_vclk(struct meson_dw_hdmi *dw_hdmi, - struct drm_display_mode *mode) + const struct drm_display_mode *mode) { struct meson_drm *priv = dw_hdmi->priv; int vic = drm_match_cea_mode(mode); + unsigned int phy_freq; unsigned int vclk_freq; unsigned int venc_freq; unsigned int hdmi_freq; vclk_freq = mode->clock; + /* For 420, pixel clock is half unlike venc clock */ + if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) + vclk_freq /= 2; + + /* TMDS clock is pixel_clock * 10 */ + phy_freq = vclk_freq * 10; + if (!vic) { - meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT, vclk_freq, - vclk_freq, vclk_freq, false); + meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT, phy_freq, + vclk_freq, vclk_freq, vclk_freq, false); return; } + /* 480i/576i needs global pixel doubling */ if (mode->flags & DRM_MODE_FLAG_DBLCLK) vclk_freq *= 2; venc_freq = vclk_freq; hdmi_freq = vclk_freq; - if (meson_venc_hdmi_venc_repeat(vic)) + /* VENC double pixels for 1080i, 720p and YUV420 modes */ + if (meson_venc_hdmi_venc_repeat(vic) || + dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) venc_freq *= 2; vclk_freq = max(venc_freq, hdmi_freq); @@ -398,11 +418,11 @@ static void dw_hdmi_set_vclk(struct meson_dw_hdmi *dw_hdmi, if (mode->flags & DRM_MODE_FLAG_DBLCLK) venc_freq /= 2; - DRM_DEBUG_DRIVER("vclk:%d venc=%d hdmi=%d enci=%d\n", - vclk_freq, venc_freq, hdmi_freq, + DRM_DEBUG_DRIVER("vclk:%d phy=%d venc=%d hdmi=%d enci=%d\n", + phy_freq, vclk_freq, venc_freq, hdmi_freq, priv->venc.hdmi_use_enci); - meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, vclk_freq, + meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, phy_freq, vclk_freq, venc_freq, hdmi_freq, priv->venc.hdmi_use_enci); } @@ -437,8 +457,9 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, /* Enable normal output to PHY */ dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); - /* TMDS pattern setup (TOFIX Handle the YUV420 case) */ - if (mode->clock > 340000) { + /* TMDS pattern setup */ + if (mode->clock > 340000 && + dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_YUV8_1X24) { dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0); dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, @@ -613,6 +634,8 @@ dw_hdmi_mode_valid(struct drm_connector *connector, const struct drm_display_mode *mode) { struct meson_drm *priv = connector->dev->dev_private; + bool is_hdmi2_sink = connector->display_info.hdmi.scdc.supported; + unsigned int phy_freq; unsigned int vclk_freq; unsigned int venc_freq; unsigned int hdmi_freq; @@ -621,9 +644,11 @@ dw_hdmi_mode_valid(struct drm_connector *connector, DRM_DEBUG_DRIVER("Modeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); - /* If sink max TMDS clock, we reject the mode */ + /* If sink does not support 540MHz, reject the non-420 HDMI2 modes */ if (connector->display_info.max_tmds_clock && - mode->clock > connector->display_info.max_tmds_clock) + mode->clock > connector->display_info.max_tmds_clock && + !drm_mode_is_420_only(&connector->display_info, mode) && + !drm_mode_is_420_also(&connector->display_info, mode)) return MODE_BAD; /* Check against non-VIC supported modes */ @@ -639,6 +664,15 @@ dw_hdmi_mode_valid(struct drm_connector *connector, vclk_freq = mode->clock; + /* For 420, pixel clock is half unlike venc clock */ + if (drm_mode_is_420_only(&connector->display_info, mode) || + (!is_hdmi2_sink && + drm_mode_is_420_also(&connector->display_info, mode))) + vclk_freq /= 2; + + /* TMDS clock is pixel_clock * 10 */ + phy_freq = vclk_freq * 10; + /* 480i/576i needs global pixel doubling */ if (mode->flags & DRM_MODE_FLAG_DBLCLK) vclk_freq *= 2; @@ -646,8 +680,11 @@ dw_hdmi_mode_valid(struct drm_connector *connector, venc_freq = vclk_freq; hdmi_freq = vclk_freq; - /* VENC double pixels for 1080i and 720p modes */ - if (meson_venc_hdmi_venc_repeat(vic)) + /* VENC double pixels for 1080i, 720p and YUV420 modes */ + if (meson_venc_hdmi_venc_repeat(vic) || + drm_mode_is_420_only(&connector->display_info, mode) || + (!is_hdmi2_sink && + drm_mode_is_420_also(&connector->display_info, mode))) venc_freq *= 2; vclk_freq = max(venc_freq, hdmi_freq); @@ -655,14 +692,19 @@ dw_hdmi_mode_valid(struct drm_connector *connector, if (mode->flags & DRM_MODE_FLAG_DBLCLK) venc_freq /= 2; - dev_dbg(connector->dev->dev, "%s: vclk:%d venc=%d hdmi=%d\n", __func__, - vclk_freq, venc_freq, hdmi_freq); + dev_dbg(connector->dev->dev, "%s: vclk:%d phy=%d venc=%d hdmi=%d\n", + __func__, phy_freq, vclk_freq, venc_freq, hdmi_freq); - return meson_vclk_vic_supported_freq(vclk_freq); + return meson_vclk_vic_supported_freq(phy_freq, vclk_freq); } /* Encoder */ +static const u32 meson_dw_hdmi_out_bus_fmts[] = { + MEDIA_BUS_FMT_YUV8_1X24, + MEDIA_BUS_FMT_UYYVYY8_0_5X24, +}; + static void meson_venc_hdmi_encoder_destroy(struct drm_encoder *encoder) { drm_encoder_cleanup(encoder); @@ -672,16 +714,54 @@ static const struct drm_encoder_funcs meson_venc_hdmi_encoder_funcs = { .destroy = meson_venc_hdmi_encoder_destroy, }; -static int meson_venc_hdmi_encoder_atomic_check(struct drm_encoder *encoder, +static u32 * +meson_venc_hdmi_encoder_get_inp_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts = NULL; + int i; + + *num_input_fmts = 0; + + for (i = 0 ; i < ARRAY_SIZE(meson_dw_hdmi_out_bus_fmts) ; ++i) { + if (output_fmt == meson_dw_hdmi_out_bus_fmts[i]) { + *num_input_fmts = 1; + input_fmts = kcalloc(*num_input_fmts, + sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + input_fmts[0] = output_fmt; + + break; + } + } + + return input_fmts; +} + +static int meson_venc_hdmi_encoder_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { + struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); + + dw_hdmi->output_bus_fmt = bridge_state->output_bus_cfg.format; + + DRM_DEBUG_DRIVER("output_bus_fmt %lx\n", dw_hdmi->output_bus_fmt); + return 0; } -static void meson_venc_hdmi_encoder_disable(struct drm_encoder *encoder) +static void meson_venc_hdmi_encoder_disable(struct drm_bridge *bridge) { - struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder); + struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); struct meson_drm *priv = dw_hdmi->priv; DRM_DEBUG_DRIVER("\n"); @@ -693,9 +773,9 @@ static void meson_venc_hdmi_encoder_disable(struct drm_encoder *encoder) writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); } -static void meson_venc_hdmi_encoder_enable(struct drm_encoder *encoder) +static void meson_venc_hdmi_encoder_enable(struct drm_bridge *bridge) { - struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder); + struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); struct meson_drm *priv = dw_hdmi->priv; DRM_DEBUG_DRIVER("%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP"); @@ -706,32 +786,47 @@ static void meson_venc_hdmi_encoder_enable(struct drm_encoder *encoder) writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); } -static void meson_venc_hdmi_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +static void meson_venc_hdmi_encoder_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) { - struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder); + struct meson_dw_hdmi *dw_hdmi = bridge_to_meson_dw_hdmi(bridge); struct meson_drm *priv = dw_hdmi->priv; int vic = drm_match_cea_mode(mode); + unsigned int ycrcb_map = VPU_HDMI_OUTPUT_CBYCR; + bool yuv420_mode = false; DRM_DEBUG_DRIVER("\"%s\" vic %d\n", mode->name, vic); + if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) { + ycrcb_map = VPU_HDMI_OUTPUT_CRYCB; + yuv420_mode = true; + } + /* VENC + VENC-DVI Mode setup */ - meson_venc_hdmi_mode_set(priv, vic, mode); + meson_venc_hdmi_mode_set(priv, vic, ycrcb_map, yuv420_mode, mode); /* VCLK Set clock */ dw_hdmi_set_vclk(dw_hdmi, mode); - /* Setup YUV444 to HDMI-TX, no 10bit diphering */ - writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); + if (dw_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) + /* Setup YUV420 to HDMI-TX, no 10bit diphering */ + writel_relaxed(2 | (2 << 2), + priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); + else + /* Setup YUV444 to HDMI-TX, no 10bit diphering */ + writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); } -static const struct drm_encoder_helper_funcs - meson_venc_hdmi_encoder_helper_funcs = { - .atomic_check = meson_venc_hdmi_encoder_atomic_check, - .disable = meson_venc_hdmi_encoder_disable, - .enable = meson_venc_hdmi_encoder_enable, - .mode_set = meson_venc_hdmi_encoder_mode_set, +static const struct drm_bridge_funcs meson_venc_hdmi_encoder_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = meson_venc_hdmi_encoder_get_inp_bus_fmts, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_check = meson_venc_hdmi_encoder_atomic_check, + .enable = meson_venc_hdmi_encoder_enable, + .disable = meson_venc_hdmi_encoder_disable, + .mode_set = meson_venc_hdmi_encoder_mode_set, }; /* DW HDMI Regmap */ @@ -852,6 +947,7 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, struct drm_device *drm = data; struct meson_drm *priv = drm->dev_private; struct dw_hdmi_plat_data *dw_plat_data; + struct drm_bridge *next_bridge; struct drm_encoder *encoder; struct resource *res; int irq; @@ -953,8 +1049,6 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, /* Encoder */ - drm_encoder_helper_add(encoder, &meson_venc_hdmi_encoder_helper_funcs); - ret = drm_encoder_init(drm, encoder, &meson_venc_hdmi_encoder_funcs, DRM_MODE_ENCODER_TMDS, "meson_hdmi"); if (ret) { @@ -962,6 +1056,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, return ret; } + meson_dw_hdmi->bridge.funcs = &meson_venc_hdmi_encoder_bridge_funcs; + drm_bridge_attach(encoder, &meson_dw_hdmi->bridge, NULL, 0); + encoder->possible_crtcs = BIT(0); DRM_DEBUG_DRIVER("encoder initialized\n"); @@ -974,8 +1071,8 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, dw_plat_data->phy_ops = &meson_dw_hdmi_phy_ops; dw_plat_data->phy_name = "meson_dw_hdmi_phy"; dw_plat_data->phy_data = meson_dw_hdmi; - dw_plat_data->input_bus_format = MEDIA_BUS_FMT_YUV8_1X24; dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709; + dw_plat_data->ycbcr_420_allowed = true; if (dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxm-dw-hdmi") || @@ -984,11 +1081,16 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, platform_set_drvdata(pdev, meson_dw_hdmi); - meson_dw_hdmi->hdmi = dw_hdmi_bind(pdev, encoder, - &meson_dw_hdmi->dw_plat_data); + meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, + &meson_dw_hdmi->dw_plat_data); if (IS_ERR(meson_dw_hdmi->hdmi)) return PTR_ERR(meson_dw_hdmi->hdmi); + next_bridge = of_drm_find_bridge(pdev->dev.of_node); + if (next_bridge) + drm_bridge_attach(encoder, next_bridge, + &meson_dw_hdmi->bridge, 0); + DRM_DEBUG_DRIVER("HDMI controller initialized\n"); return 0; |