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; |