diff options
Diffstat (limited to 'drivers/gpu/drm/vc4/vc4_hdmi.c')
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi.c | 488 | 
1 files changed, 403 insertions, 85 deletions
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 6c58b0fd13fb..823d812f4982 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -31,13 +31,14 @@   * encoder block has CEC support.   */ +#include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_scdc_helper.h>  #include <drm/drm_atomic_helper.h> -#include <drm/drm_edid.h>  #include <drm/drm_probe_helper.h>  #include <drm/drm_simple_kms_helper.h> -#include <drm/drm_scdc_helper.h>  #include <linux/clk.h>  #include <linux/component.h> +#include <linux/gpio/consumer.h>  #include <linux/i2c.h>  #include <linux/of_address.h>  #include <linux/of_gpio.h> @@ -99,17 +100,40 @@  #define HDMI_14_MAX_TMDS_CLK   (340 * 1000 * 1000) -static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode) +static const char * const output_format_str[] = { +	[VC4_HDMI_OUTPUT_RGB]		= "RGB", +	[VC4_HDMI_OUTPUT_YUV420]	= "YUV 4:2:0", +	[VC4_HDMI_OUTPUT_YUV422]	= "YUV 4:2:2", +	[VC4_HDMI_OUTPUT_YUV444]	= "YUV 4:4:4", +}; + +static const char *vc4_hdmi_output_fmt_str(enum vc4_hdmi_output_format fmt) +{ +	if (fmt >= ARRAY_SIZE(output_format_str)) +		return "invalid"; + +	return output_format_str[fmt]; +} + +static unsigned long long +vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode, +				    unsigned int bpc, enum vc4_hdmi_output_format fmt); + +static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode, +					   unsigned int bpc, +					   enum vc4_hdmi_output_format fmt)  { -	return (mode->clock * 1000) > HDMI_14_MAX_TMDS_CLK; +	unsigned long long clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt); + +	return clock > HDMI_14_MAX_TMDS_CLK;  }  static bool vc4_hdmi_is_full_range_rgb(struct vc4_hdmi *vc4_hdmi,  				       const struct drm_display_mode *mode)  { -	struct vc4_hdmi_encoder *vc4_encoder = &vc4_hdmi->encoder; +	struct drm_display_info *display = &vc4_hdmi->connector.display_info; -	return !vc4_encoder->hdmi_monitor || +	return !display->is_hdmi ||  		drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_FULL;  } @@ -216,12 +240,11 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)  			if (edid) {  				cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid); -				vc4_hdmi->encoder.hdmi_monitor = drm_detect_hdmi_monitor(edid);  				kfree(edid);  			}  		} -		vc4_hdmi_enable_scrambling(&vc4_hdmi->encoder.base.base); +		vc4_hdmi_enable_scrambling(&vc4_hdmi->encoder.base);  		pm_runtime_put(&vc4_hdmi->pdev->dev);  		mutex_unlock(&vc4_hdmi->mutex);  		return connector_status_connected; @@ -242,7 +265,6 @@ static void vc4_hdmi_connector_destroy(struct drm_connector *connector)  static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)  {  	struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); -	struct vc4_hdmi_encoder *vc4_encoder = &vc4_hdmi->encoder;  	int ret = 0;  	struct edid *edid; @@ -255,8 +277,6 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)  		goto out;  	} -	vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid); -  	drm_connector_update_edid_property(connector, edid);  	ret = drm_add_edid_modes(connector, edid);  	kfree(edid); @@ -266,7 +286,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)  		struct drm_display_mode *mode;  		list_for_each_entry(mode, &connector->probed_modes, head) { -			if (vc4_hdmi_mode_needs_scrambling(mode)) { +			if (vc4_hdmi_mode_needs_scrambling(mode, 8, VC4_HDMI_OUTPUT_RGB)) {  				drm_warn_once(drm, "The core clock cannot reach frequencies high enough to support 4k @ 60Hz.");  				drm_warn_once(drm, "Please change your config.txt file to add hdmi_enable_4kp60.");  			} @@ -323,6 +343,7 @@ static void vc4_hdmi_connector_reset(struct drm_connector *connector)  	new_state->base.max_bpc = 8;  	new_state->base.max_requested_bpc = 8; +	new_state->output_format = VC4_HDMI_OUTPUT_RGB;  	drm_atomic_helper_connector_tv_reset(connector);  } @@ -337,7 +358,9 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector)  	if (!new_state)  		return NULL; -	new_state->pixel_rate = vc4_state->pixel_rate; +	new_state->tmds_char_rate = vc4_state->tmds_char_rate; +	new_state->output_bpc = vc4_state->output_bpc; +	new_state->output_format = vc4_state->output_format;  	__drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);  	return &new_state->base; @@ -361,7 +384,7 @@ static int vc4_hdmi_connector_init(struct drm_device *dev,  				   struct vc4_hdmi *vc4_hdmi)  {  	struct drm_connector *connector = &vc4_hdmi->connector; -	struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; +	struct drm_encoder *encoder = &vc4_hdmi->encoder.base;  	int ret;  	drm_connector_init_with_ddc(dev, connector, @@ -481,11 +504,38 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,  		DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret);  } +static void vc4_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, +					      enum vc4_hdmi_output_format fmt) +{ +	switch (fmt) { +	case VC4_HDMI_OUTPUT_RGB: +		frame->colorspace = HDMI_COLORSPACE_RGB; +		break; + +	case VC4_HDMI_OUTPUT_YUV420: +		frame->colorspace = HDMI_COLORSPACE_YUV420; +		break; + +	case VC4_HDMI_OUTPUT_YUV422: +		frame->colorspace = HDMI_COLORSPACE_YUV422; +		break; + +	case VC4_HDMI_OUTPUT_YUV444: +		frame->colorspace = HDMI_COLORSPACE_YUV444; +		break; + +	default: +		break; +	} +} +  static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)  {  	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);  	struct drm_connector *connector = &vc4_hdmi->connector;  	struct drm_connector_state *cstate = connector->state; +	struct vc4_hdmi_connector_state *vc4_state = +		conn_state_to_vc4_hdmi_conn_state(cstate);  	const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;  	union hdmi_infoframe frame;  	int ret; @@ -505,6 +555,7 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)  					   HDMI_QUANTIZATION_RANGE_FULL :  					   HDMI_QUANTIZATION_RANGE_LIMITED);  	drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate); +	vc4_hdmi_avi_infoframe_colorspace(&frame.avi, vc4_state->output_format);  	drm_hdmi_avi_infoframe_bars(&frame.avi, cstate);  	vc4_hdmi_write_infoframe(encoder, &frame); @@ -578,13 +629,12 @@ static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder)  static bool vc4_hdmi_supports_scrambling(struct drm_encoder *encoder,  					 struct drm_display_mode *mode)  { -	struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder);  	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);  	struct drm_display_info *display = &vc4_hdmi->connector.display_info;  	lockdep_assert_held(&vc4_hdmi->mutex); -	if (!vc4_encoder->hdmi_monitor) +	if (!display->is_hdmi)  		return false;  	if (!display->hdmi.scdc.supported || @@ -607,7 +657,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder)  	if (!vc4_hdmi_supports_scrambling(encoder, mode))  		return; -	if (!vc4_hdmi_mode_needs_scrambling(mode)) +	if (!vc4_hdmi_mode_needs_scrambling(mode, +					    vc4_hdmi->output_bpc, +					    vc4_hdmi->output_format))  		return;  	drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true); @@ -802,6 +854,39 @@ static const u16 vc5_hdmi_csc_full_rgb_to_limited_rgb[3][4] = {  	{ 0x0000, 0x0000, 0x1b80, 0x0400 },  }; +/* + * Conversion between Full Range RGB and Full Range YUV422 using the + * BT.709 Colorspace + * + * + * [  0.181906  0.611804  0.061758  16  ] + * [ -0.100268 -0.337232  0.437500  128 ] + * [  0.437500 -0.397386 -0.040114  128 ] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_to_limited_yuv422_bt709[3][4] = { +	{ 0x05d2, 0x1394, 0x01fa, 0x0400 }, +	{ 0xfccc, 0xf536, 0x0e00, 0x2000 }, +	{ 0x0e00, 0xf34a, 0xfeb8, 0x2000 }, +}; + +/* + * Conversion between Full Range RGB and Full Range YUV444 using the + * BT.709 Colorspace + * + * [ -0.100268 -0.337232  0.437500  128 ] + * [  0.437500 -0.397386 -0.040114  128 ] + * [  0.181906  0.611804  0.061758  16  ] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_to_limited_yuv444_bt709[3][4] = { +	{ 0xfccc, 0xf536, 0x0e00, 0x2000 }, +	{ 0x0e00, 0xf34a, 0xfeb8, 0x2000 }, +	{ 0x05d2, 0x1394, 0x01fa, 0x0400 }, +}; +  static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi,  				    const u16 coeffs[3][4])  { @@ -819,19 +904,53 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,  			       struct drm_connector_state *state,  			       const struct drm_display_mode *mode)  { +	struct vc4_hdmi_connector_state *vc4_state = +		conn_state_to_vc4_hdmi_conn_state(state);  	unsigned long flags; +	u32 if_cfg = 0; +	u32 if_xbar = 0x543210; +	u32 csc_chan_ctl = 0;  	u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM,  							       VC5_MT_CP_CSC_CTL_MODE);  	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); -	HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); +	switch (vc4_state->output_format) { +	case VC4_HDMI_OUTPUT_YUV444: +		vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_yuv444_bt709); +		break; -	if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) -		vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_rgb); -	else -		vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_unity); +	case VC4_HDMI_OUTPUT_YUV422: +		csc_ctl |= VC4_SET_FIELD(VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_STANDARD, +					 VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422) | +			VC5_MT_CP_CSC_CTL_USE_444_TO_422 | +			VC5_MT_CP_CSC_CTL_USE_RNG_SUPPRESSION; + +		csc_chan_ctl |= VC4_SET_FIELD(VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_LEGACY_STYLE, +					      VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP); + +		if_cfg |= VC4_SET_FIELD(VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_FORMAT_422_LEGACY, +					VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422); + +		vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_yuv422_bt709); +		break; +	case VC4_HDMI_OUTPUT_RGB: +		if_xbar = 0x354021; + +		if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) +			vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_rgb); +		else +			vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_unity); +		break; + +	default: +		break; +	} + +	HDMI_WRITE(HDMI_VEC_INTERFACE_CFG, if_cfg); +	HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, if_xbar); +	HDMI_WRITE(HDMI_CSC_CHANNEL_CTL, csc_chan_ctl);  	HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);  	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); @@ -892,6 +1011,8 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,  				 struct drm_connector_state *state,  				 struct drm_display_mode *mode)  { +	const struct vc4_hdmi_connector_state *vc4_state = +		conn_state_to_vc4_hdmi_conn_state(state);  	bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;  	bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;  	bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; @@ -939,7 +1060,7 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,  	HDMI_WRITE(HDMI_VERTB0, vertb_even);  	HDMI_WRITE(HDMI_VERTB1, vertb); -	switch (state->max_bpc) { +	switch (vc4_state->output_bpc) {  	case 12:  		gcp = 6;  		gcp_en = true; @@ -955,6 +1076,15 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,  		break;  	} +	/* +	 * YCC422 is always 36-bit and not considered deep colour so +	 * doesn't signal in GCP. +	 */ +	if (vc4_state->output_format == VC4_HDMI_OUTPUT_YUV422) { +		gcp = 4; +		gcp_en = false; +	} +  	reg = HDMI_READ(HDMI_DEEP_COLOR_CONFIG_1);  	reg &= ~(VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_MASK |  		 VC5_HDMI_DEEP_COLOR_CONFIG_1_COLOR_DEPTH_MASK); @@ -1022,7 +1152,7 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,  	struct vc4_hdmi_connector_state *vc4_conn_state =  		conn_state_to_vc4_hdmi_conn_state(conn_state);  	struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; -	unsigned long pixel_rate = vc4_conn_state->pixel_rate; +	unsigned long tmds_char_rate = vc4_conn_state->tmds_char_rate;  	unsigned long bvb_rate, hsm_rate;  	unsigned long flags;  	int ret; @@ -1045,7 +1175,7 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,  	 * Additionally, the AXI clock needs to be at least 25% of  	 * pixel clock, but HSM ends up being the limiting factor.  	 */ -	hsm_rate = max_t(unsigned long, 120000000, (pixel_rate / 100) * 101); +	hsm_rate = max_t(unsigned long, 120000000, (tmds_char_rate / 100) * 101);  	ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate);  	if (ret) {  		DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); @@ -1058,7 +1188,7 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,  		goto out;  	} -	ret = clk_set_rate(vc4_hdmi->pixel_clock, pixel_rate); +	ret = clk_set_rate(vc4_hdmi->pixel_clock, tmds_char_rate);  	if (ret) {  		DRM_ERROR("Failed to set pixel clock rate: %d\n", ret);  		goto err_put_runtime_pm; @@ -1073,9 +1203,9 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,  	vc4_hdmi_cec_update_clk_div(vc4_hdmi); -	if (pixel_rate > 297000000) +	if (tmds_char_rate > 297000000)  		bvb_rate = 300000000; -	else if (pixel_rate > 148500000) +	else if (tmds_char_rate > 148500000)  		bvb_rate = 150000000;  	else  		bvb_rate = 75000000; @@ -1147,7 +1277,7 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,  {  	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);  	struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; -	struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); +	struct drm_display_info *display = &vc4_hdmi->connector.display_info;  	bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;  	bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;  	unsigned long flags; @@ -1168,7 +1298,7 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,  	HDMI_WRITE(HDMI_VID_CTL,  		   HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_BLANKPIX); -	if (vc4_encoder->hdmi_monitor) { +	if (display->is_hdmi) {  		HDMI_WRITE(HDMI_SCHEDULER_CONTROL,  			   HDMI_READ(HDMI_SCHEDULER_CONTROL) |  			   VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI); @@ -1195,7 +1325,7 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,  			  "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");  	} -	if (vc4_encoder->hdmi_monitor) { +	if (display->is_hdmi) {  		spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);  		WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) & @@ -1232,13 +1362,234 @@ static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder,  					     struct drm_connector_state *conn_state)  {  	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); +	struct vc4_hdmi_connector_state *vc4_state = +		conn_state_to_vc4_hdmi_conn_state(conn_state);  	mutex_lock(&vc4_hdmi->mutex);  	drm_mode_copy(&vc4_hdmi->saved_adjusted_mode,  		      &crtc_state->adjusted_mode); +	vc4_hdmi->output_bpc = vc4_state->output_bpc; +	vc4_hdmi->output_format = vc4_state->output_format;  	mutex_unlock(&vc4_hdmi->mutex);  } +static bool +vc4_hdmi_sink_supports_format_bpc(const struct vc4_hdmi *vc4_hdmi, +				  const struct drm_display_info *info, +				  const struct drm_display_mode *mode, +				  unsigned int format, unsigned int bpc) +{ +	struct drm_device *dev = vc4_hdmi->connector.dev; +	u8 vic = drm_match_cea_mode(mode); + +	if (vic == 1 && bpc != 8) { +		drm_dbg(dev, "VIC1 requires a bpc of 8, got %u\n", bpc); +		return false; +	} + +	if (!info->is_hdmi && +	    (format != VC4_HDMI_OUTPUT_RGB || bpc != 8)) { +		drm_dbg(dev, "DVI Monitors require an RGB output at 8 bpc\n"); +		return false; +	} + +	switch (format) { +	case VC4_HDMI_OUTPUT_RGB: +		drm_dbg(dev, "RGB Format, checking the constraints.\n"); + +		if (!(info->color_formats & DRM_COLOR_FORMAT_RGB444)) +			return false; + +		if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) { +			drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n"); +			return false; +		} + +		if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) { +			drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n"); +			return false; +		} + +		drm_dbg(dev, "RGB format supported in that configuration.\n"); + +		return true; + +	case VC4_HDMI_OUTPUT_YUV422: +		drm_dbg(dev, "YUV422 format, checking the constraints.\n"); + +		if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR422)) { +			drm_dbg(dev, "Sink doesn't support YUV422.\n"); +			return false; +		} + +		if (bpc != 12) { +			drm_dbg(dev, "YUV422 only supports 12 bpc.\n"); +			return false; +		} + +		drm_dbg(dev, "YUV422 format supported in that configuration.\n"); + +		return true; + +	case VC4_HDMI_OUTPUT_YUV444: +		drm_dbg(dev, "YUV444 format, checking the constraints.\n"); + +		if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR444)) { +			drm_dbg(dev, "Sink doesn't support YUV444.\n"); +			return false; +		} + +		if (bpc == 10 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_30)) { +			drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n"); +			return false; +		} + +		if (bpc == 12 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_36)) { +			drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n"); +			return false; +		} + +		drm_dbg(dev, "YUV444 format supported in that configuration.\n"); + +		return true; +	} + +	return false; +} + +static enum drm_mode_status +vc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi, +			     unsigned long long clock) +{ +	const struct drm_connector *connector = &vc4_hdmi->connector; +	const struct drm_display_info *info = &connector->display_info; + +	if (clock > vc4_hdmi->variant->max_pixel_clock) +		return MODE_CLOCK_HIGH; + +	if (vc4_hdmi->disable_4kp60 && clock > HDMI_14_MAX_TMDS_CLK) +		return MODE_CLOCK_HIGH; + +	if (info->max_tmds_clock && clock > (info->max_tmds_clock * 1000)) +		return MODE_CLOCK_HIGH; + +	return MODE_OK; +} + +static unsigned long long +vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode, +				    unsigned int bpc, +				    enum vc4_hdmi_output_format fmt) +{ +	unsigned long long clock = mode->clock * 1000; + +	if (mode->flags & DRM_MODE_FLAG_DBLCLK) +		clock = clock * 2; + +	if (fmt == VC4_HDMI_OUTPUT_YUV422) +		bpc = 8; + +	clock = clock * bpc; +	do_div(clock, 8); + +	return clock; +} + +static int +vc4_hdmi_encoder_compute_clock(const struct vc4_hdmi *vc4_hdmi, +			       struct vc4_hdmi_connector_state *vc4_state, +			       const struct drm_display_mode *mode, +			       unsigned int bpc, unsigned int fmt) +{ +	unsigned long long clock; + +	clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt); +	if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, clock) != MODE_OK) +		return -EINVAL; + +	vc4_state->tmds_char_rate = clock; + +	return 0; +} + +static int +vc4_hdmi_encoder_compute_format(const struct vc4_hdmi *vc4_hdmi, +				struct vc4_hdmi_connector_state *vc4_state, +				const struct drm_display_mode *mode, +				unsigned int bpc) +{ +	struct drm_device *dev = vc4_hdmi->connector.dev; +	const struct drm_connector *connector = &vc4_hdmi->connector; +	const struct drm_display_info *info = &connector->display_info; +	unsigned int format; + +	drm_dbg(dev, "Trying with an RGB output\n"); + +	format = VC4_HDMI_OUTPUT_RGB; +	if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) { +		int ret; + +		ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state, +						     mode, bpc, format); +		if (!ret) { +			vc4_state->output_format = format; +			return 0; +		} +	} + +	drm_dbg(dev, "Failed, Trying with an YUV422 output\n"); + +	format = VC4_HDMI_OUTPUT_YUV422; +	if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) { +		int ret; + +		ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state, +						     mode, bpc, format); +		if (!ret) { +			vc4_state->output_format = format; +			return 0; +		} +	} + +	drm_dbg(dev, "Failed. No Format Supported for that bpc count.\n"); + +	return -EINVAL; +} + +static int +vc4_hdmi_encoder_compute_config(const struct vc4_hdmi *vc4_hdmi, +				struct vc4_hdmi_connector_state *vc4_state, +				const struct drm_display_mode *mode) +{ +	struct drm_device *dev = vc4_hdmi->connector.dev; +	struct drm_connector_state *conn_state = &vc4_state->base; +	unsigned int max_bpc = clamp_t(unsigned int, conn_state->max_bpc, 8, 12); +	unsigned int bpc; +	int ret; + +	for (bpc = max_bpc; bpc >= 8; bpc -= 2) { +		drm_dbg(dev, "Trying with a %d bpc output\n", bpc); + +		ret = vc4_hdmi_encoder_compute_format(vc4_hdmi, vc4_state, +						      mode, bpc); +		if (ret) +			continue; + +		vc4_state->output_bpc = bpc; + +		drm_dbg(dev, +			"Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n", +			mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode), +			vc4_state->output_bpc, +			vc4_hdmi_output_fmt_str(vc4_state->output_format), +			vc4_state->tmds_char_rate); + +		break; +	} + +	return ret; +} +  #define WIFI_2_4GHz_CH1_MIN_FREQ	2400000000ULL  #define WIFI_2_4GHz_CH1_MAX_FREQ	2422000000ULL @@ -1249,8 +1600,9 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,  	struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(conn_state);  	struct drm_display_mode *mode = &crtc_state->adjusted_mode;  	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); -	unsigned long long pixel_rate = mode->clock * 1000; -	unsigned long long tmds_rate; +	unsigned long long tmds_char_rate = mode->clock * 1000; +	unsigned long long tmds_bit_rate; +	int ret;  	if (vc4_hdmi->variant->unsupported_odd_h_timings &&  	    !(mode->flags & DRM_MODE_FLAG_DBLCLK) && @@ -1264,32 +1616,17 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,  	 * bandwidth). Slightly lower the frequency to bring it out of  	 * the WiFi range.  	 */ -	tmds_rate = pixel_rate * 10; +	tmds_bit_rate = tmds_char_rate * 10;  	if (vc4_hdmi->disable_wifi_frequencies && -	    (tmds_rate >= WIFI_2_4GHz_CH1_MIN_FREQ && -	     tmds_rate <= WIFI_2_4GHz_CH1_MAX_FREQ)) { +	    (tmds_bit_rate >= WIFI_2_4GHz_CH1_MIN_FREQ && +	     tmds_bit_rate <= WIFI_2_4GHz_CH1_MAX_FREQ)) {  		mode->clock = 238560; -		pixel_rate = mode->clock * 1000; -	} - -	if (conn_state->max_bpc == 12) { -		pixel_rate = pixel_rate * 150; -		do_div(pixel_rate, 100); -	} else if (conn_state->max_bpc == 10) { -		pixel_rate = pixel_rate * 125; -		do_div(pixel_rate, 100); +		tmds_char_rate = mode->clock * 1000;  	} -	if (mode->flags & DRM_MODE_FLAG_DBLCLK) -		pixel_rate = pixel_rate * 2; - -	if (pixel_rate > vc4_hdmi->variant->max_pixel_clock) -		return -EINVAL; - -	if (vc4_hdmi->disable_4kp60 && (pixel_rate > HDMI_14_MAX_TMDS_CLK)) -		return -EINVAL; - -	vc4_state->pixel_rate = pixel_rate; +	ret = vc4_hdmi_encoder_compute_config(vc4_hdmi, vc4_state, mode); +	if (ret) +		return ret;  	return 0;  } @@ -1306,13 +1643,7 @@ vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,  	     (mode->hsync_end % 2) || (mode->htotal % 2)))  		return MODE_H_ILLEGAL; -	if ((mode->clock * 1000) > vc4_hdmi->variant->max_pixel_clock) -		return MODE_CLOCK_HIGH; - -	if (vc4_hdmi->disable_4kp60 && vc4_hdmi_mode_needs_scrambling(mode)) -		return MODE_CLOCK_HIGH; - -	return MODE_OK; +	return vc4_hdmi_encoder_clock_valid(vc4_hdmi, mode->clock * 1000);  }  static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { @@ -1468,7 +1799,7 @@ static int vc4_hdmi_audio_startup(struct device *dev, void *data)  static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi)  { -	struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; +	struct drm_encoder *encoder = &vc4_hdmi->encoder.base;  	struct device *dev = &vc4_hdmi->pdev->dev;  	unsigned long flags;  	int ret; @@ -1558,7 +1889,7 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data,  				  struct hdmi_codec_params *params)  {  	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); -	struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; +	struct drm_encoder *encoder = &vc4_hdmi->encoder.base;  	unsigned int sample_rate = params->sample_rate;  	unsigned int channels = params->channels;  	unsigned long flags; @@ -2511,13 +2842,13 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)  	INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq);  	dev_set_drvdata(dev, vc4_hdmi); -	encoder = &vc4_hdmi->encoder.base.base; -	vc4_hdmi->encoder.base.type = variant->encoder_type; -	vc4_hdmi->encoder.base.pre_crtc_configure = vc4_hdmi_encoder_pre_crtc_configure; -	vc4_hdmi->encoder.base.pre_crtc_enable = vc4_hdmi_encoder_pre_crtc_enable; -	vc4_hdmi->encoder.base.post_crtc_enable = vc4_hdmi_encoder_post_crtc_enable; -	vc4_hdmi->encoder.base.post_crtc_disable = vc4_hdmi_encoder_post_crtc_disable; -	vc4_hdmi->encoder.base.post_crtc_powerdown = vc4_hdmi_encoder_post_crtc_powerdown; +	encoder = &vc4_hdmi->encoder.base; +	vc4_hdmi->encoder.type = variant->encoder_type; +	vc4_hdmi->encoder.pre_crtc_configure = vc4_hdmi_encoder_pre_crtc_configure; +	vc4_hdmi->encoder.pre_crtc_enable = vc4_hdmi_encoder_pre_crtc_enable; +	vc4_hdmi->encoder.post_crtc_enable = vc4_hdmi_encoder_post_crtc_enable; +	vc4_hdmi->encoder.post_crtc_disable = vc4_hdmi_encoder_post_crtc_disable; +	vc4_hdmi->encoder.post_crtc_powerdown = vc4_hdmi_encoder_post_crtc_powerdown;  	vc4_hdmi->pdev = pdev;  	vc4_hdmi->variant = variant; @@ -2568,19 +2899,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)  	}  	/* -	 * If we boot without any cable connected to the HDMI connector, -	 * the firmware will skip the HSM initialization and leave it -	 * with a rate of 0, resulting in a bus lockup when we're -	 * accessing the registers even if it's enabled. -	 * -	 * Let's put a sensible default at runtime_resume so that we -	 * don't end up in this situation. -	 */ -	ret = clk_set_min_rate(vc4_hdmi->hsm_clock, HSM_MIN_CLOCK_FREQ); -	if (ret) -		goto err_put_ddc; - -	/*  	 * We need to have the device powered up at this point to call  	 * our reset hook and for the CEC init.  	 */ @@ -2679,7 +2997,7 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master,  	vc4_hdmi_cec_exit(vc4_hdmi);  	vc4_hdmi_hotplug_exit(vc4_hdmi);  	vc4_hdmi_connector_destroy(&vc4_hdmi->connector); -	drm_encoder_cleanup(&vc4_hdmi->encoder.base.base); +	drm_encoder_cleanup(&vc4_hdmi->encoder.base);  	pm_runtime_disable(dev);  |