Hi,
This is another attempt at supporting the HDMI YUV output in the vc4 HDMI driver.
This is a follow-up of https://lore.kernel.org/dri-devel/20210317154352.732095-1-maxime@cerno.tech/
And the discussions that occured recently on the mailing lists and IRC about this.
The series mentioned above had multiple issues, the main one being that it was a bit too much complicated for what we wanted to achieve. This series is taking a much simpler approach with an ad-hoc solution.
I think some parts of it could still be moved to KMS helpers (notably, the output format enum, and the helper to set the infoframe for it) and structures (the output format stored in drm_connector_state). This would also interact nicely with the work done here:
https://lore.kernel.org/dri-devel/20211118103814.524670-1-maxime@cerno.tech/
This can come as a second step though.
The other issues with the first attempt was that nothing was reported to userspace about the decision we made about the format, and that this decision was essentially policy, without any way for the userspace to influence it.
Those two points however are being worked on by Werner in a cross-driver effort:
https://lore.kernel.org/dri-devel/e452775c-5b95-bbfd-e818-f1480f556336@tuxed...
Since it's a KMS decision, I don't think we should hold off any driver as long as it's consistent with what the other drivers are doing.
Let me know what you think, Maxime
---
Changes from v1: - Fixed an EDID parsing error for YUV422 - Fixed the scrambling setup when using a bpc > 8 - Added some logging - Fixed some build-bot warnings - Fixed a number of HDMI specifications and EDID issues - Try to max out the bpc every time
Maxime Ripard (14): drm/edid: Don't clear YUV422 if using deep color drm/edid: Rename drm_hdmi_avi_infoframe_colorspace to _colorimetry drm/vc4: hdmi: Add full range RGB helper drm/vc4: hdmi: Use full range helper in csc functions drm/vc4: hdmi: Move XBAR setup to csc_setup drm/vc4: hdmi: Replace CSC_CTL hardcoded value by defines drm/vc4: hdmi: Define colorspace matrices drm/vc4: hdmi: Change CSC callback prototype drm/vc4: hdmi: Move clock validation to its own function drm/vc4: hdmi: Move clock calculation into its own function drm/vc4: hdmi: Take the sink maximum TMDS clock into account drm/vc4: hdmi: Take bpp into account for the scrambler drm/vc4: hdmi: Always try to have the highest bpc drm/vc4: hdmi: Support HDMI YUV output
drivers/gpu/drm/drm_edid.c | 13 +- drivers/gpu/drm/i915/display/intel_hdmi.c | 2 +- drivers/gpu/drm/i915/display/intel_lspcon.c | 2 +- drivers/gpu/drm/vc4/vc4_hdmi.c | 519 ++++++++++++++++---- drivers/gpu/drm/vc4/vc4_hdmi.h | 26 +- drivers/gpu/drm/vc4/vc4_hdmi_regs.h | 6 + drivers/gpu/drm/vc4/vc4_regs.h | 19 + include/drm/drm_edid.h | 4 +- 8 files changed, 495 insertions(+), 96 deletions(-)
The current code, when parsing the EDID Deep Color depths, that the YUV422 cannot be used, referring to the HDMI 1.3 Specification.
This specification, in its section 6.2.4, indeed states:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is not permitted for any Deep Color mode.
This indeed can be interpreted like the code does, but the HDMI 1.4 specification further clarifies that statement in its section 6.2.4:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is also 36-bit mode but does not require the further use of the Deep Color modes described in section 6.5.2 and 6.5.3.
This means that, even though YUV422 can be used with 12 bit per color, it shouldn't be treated as a deep color mode.
This deviates from the interpretation of the code and comment, so let's fix those.
Fixes: d0c94692e0a3 ("drm/edid: Parse and handle HDMI deep color modes.") Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_edid.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 12893e7be89b..e57d1b8cdaaa 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5106,10 +5106,9 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector,
/* * Deep color support mandates RGB444 support for all video - * modes and forbids YCRCB422 support for all video modes per - * HDMI 1.3 spec. + * modes. */ - info->color_formats = DRM_COLOR_FORMAT_RGB444; + info->color_formats |= DRM_COLOR_FORMAT_RGB444;
/* YCRCB444 is optional according to spec. */ if (hdmi[6] & DRM_EDID_HDMI_DC_Y444) {
On Wed, Dec 15, 2021 at 01:43:53PM +0100, Maxime Ripard wrote:
The current code, when parsing the EDID Deep Color depths, that the YUV422 cannot be used, referring to the HDMI 1.3 Specification.
This specification, in its section 6.2.4, indeed states:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is not permitted for any Deep Color mode.
This indeed can be interpreted like the code does, but the HDMI 1.4 specification further clarifies that statement in its section 6.2.4:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is also 36-bit mode but does not require the further use of the Deep Color modes described in section 6.5.2 and 6.5.3.
This means that, even though YUV422 can be used with 12 bit per color, it shouldn't be treated as a deep color mode.
This deviates from the interpretation of the code and comment, so let's fix those.
Fixes: d0c94692e0a3 ("drm/edid: Parse and handle HDMI deep color modes.") Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/drm_edid.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 12893e7be89b..e57d1b8cdaaa 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5106,10 +5106,9 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector,
/* * Deep color support mandates RGB444 support for all video
* modes and forbids YCRCB422 support for all video modes per
* HDMI 1.3 spec.
*/* modes.
- info->color_formats = DRM_COLOR_FORMAT_RGB444;
info->color_formats |= DRM_COLOR_FORMAT_RGB444;
/* YCRCB444 is optional according to spec. */ if (hdmi[6] & DRM_EDID_HDMI_DC_Y444) {
This whole code seems pretty much wrong. What it tries to do (I think) is make sure we don't use deep color with YCbCr 4:4:4 unless supported. But what it actually does is also disable YCbCr 4:4:4 8bpc when deep color is not supported for YCbCr 4:4:4.
I think what we want is to just get rid of this color_formats stuff here entirely and instead have some kind of separate tracking of RGB 4:4:4 vs. YCbCr 4:4:4 deep color capabilities.
Hi Ville,
Thanks for your review
On Wed, Dec 15, 2021 at 03:48:39PM +0200, Ville Syrjälä wrote:
On Wed, Dec 15, 2021 at 01:43:53PM +0100, Maxime Ripard wrote:
The current code, when parsing the EDID Deep Color depths, that the YUV422 cannot be used, referring to the HDMI 1.3 Specification.
This specification, in its section 6.2.4, indeed states:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is not permitted for any Deep Color mode.
This indeed can be interpreted like the code does, but the HDMI 1.4 specification further clarifies that statement in its section 6.2.4:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is also 36-bit mode but does not require the further use of the Deep Color modes described in section 6.5.2 and 6.5.3.
This means that, even though YUV422 can be used with 12 bit per color, it shouldn't be treated as a deep color mode.
This deviates from the interpretation of the code and comment, so let's fix those.
Fixes: d0c94692e0a3 ("drm/edid: Parse and handle HDMI deep color modes.") Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/drm_edid.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 12893e7be89b..e57d1b8cdaaa 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5106,10 +5106,9 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector,
/* * Deep color support mandates RGB444 support for all video
* modes and forbids YCRCB422 support for all video modes per
* HDMI 1.3 spec.
*/* modes.
- info->color_formats = DRM_COLOR_FORMAT_RGB444;
info->color_formats |= DRM_COLOR_FORMAT_RGB444;
/* YCRCB444 is optional according to spec. */ if (hdmi[6] & DRM_EDID_HDMI_DC_Y444) {
This whole code seems pretty much wrong. What it tries to do (I think) is make sure we don't use deep color with YCbCr 4:4:4 unless supported. But what it actually does is also disable YCbCr 4:4:4 8bpc when deep color is not supported for YCbCr 4:4:4.
I think what we want is to just get rid of this color_formats stuff here entirely and instead have some kind of separate tracking of RGB 4:4:4 vs. YCbCr 4:4:4 deep color capabilities.
I'm sorry, I'm not entirely sure to understand what you're suggesting here. Do you want to get ride of info->color_formats entirely?
Maxime
On Tue, Jan 11, 2022 at 04:55:35PM +0100, Maxime Ripard wrote:
Hi Ville,
Thanks for your review
On Wed, Dec 15, 2021 at 03:48:39PM +0200, Ville Syrjälä wrote:
On Wed, Dec 15, 2021 at 01:43:53PM +0100, Maxime Ripard wrote:
The current code, when parsing the EDID Deep Color depths, that the YUV422 cannot be used, referring to the HDMI 1.3 Specification.
This specification, in its section 6.2.4, indeed states:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is not permitted for any Deep Color mode.
This indeed can be interpreted like the code does, but the HDMI 1.4 specification further clarifies that statement in its section 6.2.4:
For each supported Deep Color mode, RGB 4:4:4 shall be supported and optionally YCBCR 4:4:4 may be supported.
YCBCR 4:2:2 is also 36-bit mode but does not require the further use of the Deep Color modes described in section 6.5.2 and 6.5.3.
This means that, even though YUV422 can be used with 12 bit per color, it shouldn't be treated as a deep color mode.
This deviates from the interpretation of the code and comment, so let's fix those.
Fixes: d0c94692e0a3 ("drm/edid: Parse and handle HDMI deep color modes.") Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/drm_edid.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 12893e7be89b..e57d1b8cdaaa 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5106,10 +5106,9 @@ static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector,
/* * Deep color support mandates RGB444 support for all video
* modes and forbids YCRCB422 support for all video modes per
* HDMI 1.3 spec.
*/* modes.
- info->color_formats = DRM_COLOR_FORMAT_RGB444;
info->color_formats |= DRM_COLOR_FORMAT_RGB444;
/* YCRCB444 is optional according to spec. */ if (hdmi[6] & DRM_EDID_HDMI_DC_Y444) {
This whole code seems pretty much wrong. What it tries to do (I think) is make sure we don't use deep color with YCbCr 4:4:4 unless supported. But what it actually does is also disable YCbCr 4:4:4 8bpc when deep color is not supported for YCbCr 4:4:4.
I think what we want is to just get rid of this color_formats stuff here entirely and instead have some kind of separate tracking of RGB 4:4:4 vs. YCbCr 4:4:4 deep color capabilities.
I'm sorry, I'm not entirely sure to understand what you're suggesting here. Do you want to get ride of info->color_formats entirely?
I think color_formats can stay (assuming it's actually useful for something), but we need separate tracking for supported RGB 4:4:4 vs. YCbCr 4:4:4 deep color modes.
The drm_hdmi_avi_infoframe_colorspace() function actually sets the colorimetry and extended_colorimetry fields in the hdmi_avi_infoframe structure with DRM_MODE_COLORIMETRY_* values.
To make things worse, the hdmi_avi_infoframe structure also has a colorspace field used to signal whether an RGB or YUV output is being used.
Let's remove the inconsistency and allow for the colorspace usage by renaming the function.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_edid.c | 8 ++++---- drivers/gpu/drm/i915/display/intel_hdmi.c | 2 +- drivers/gpu/drm/i915/display/intel_lspcon.c | 2 +- drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- include/drm/drm_edid.h | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index e57d1b8cdaaa..dec7aecfc655 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5890,13 +5890,13 @@ static const u32 hdmi_colorimetry_val[] = { #undef ACE
/** - * drm_hdmi_avi_infoframe_colorspace() - fill the HDMI AVI infoframe - * colorspace information + * drm_hdmi_avi_infoframe_colorimetry() - fill the HDMI AVI infoframe + * colorimetry information * @frame: HDMI AVI infoframe * @conn_state: connector state */ void -drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, +drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame, const struct drm_connector_state *conn_state) { u32 colorimetry_val; @@ -5915,7 +5915,7 @@ drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, frame->extended_colorimetry = (colorimetry_val >> 2) & EXTENDED_COLORIMETRY_MASK; } -EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorspace); +EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorimetry);
/** * drm_hdmi_avi_infoframe_quant_range() - fill the HDMI AVI infoframe diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.c b/drivers/gpu/drm/i915/display/intel_hdmi.c index 3b5b9e7b05b7..96e508ddc4af 100644 --- a/drivers/gpu/drm/i915/display/intel_hdmi.c +++ b/drivers/gpu/drm/i915/display/intel_hdmi.c @@ -730,7 +730,7 @@ intel_hdmi_compute_avi_infoframe(struct intel_encoder *encoder, else frame->colorspace = HDMI_COLORSPACE_RGB;
- drm_hdmi_avi_infoframe_colorspace(frame, conn_state); + drm_hdmi_avi_infoframe_colorimetry(frame, conn_state);
/* nonsense combination */ drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range && diff --git a/drivers/gpu/drm/i915/display/intel_lspcon.c b/drivers/gpu/drm/i915/display/intel_lspcon.c index 05d2d750fa53..092a925c6cf5 100644 --- a/drivers/gpu/drm/i915/display/intel_lspcon.c +++ b/drivers/gpu/drm/i915/display/intel_lspcon.c @@ -537,7 +537,7 @@ void lspcon_set_infoframes(struct intel_encoder *encoder, frame.avi.colorspace = HDMI_COLORSPACE_RGB;
/* Set the Colorspace as per the HDMI spec */ - drm_hdmi_avi_infoframe_colorspace(&frame.avi, conn_state); + drm_hdmi_avi_infoframe_colorimetry(&frame.avi, conn_state);
/* nonsense combination */ drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range && diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 053fbaf765ca..be39e55ae113 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -502,7 +502,7 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) vc4_encoder->limited_rgb_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL); - drm_hdmi_avi_infoframe_colorspace(&frame.avi, cstate); + drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate); drm_hdmi_avi_infoframe_bars(&frame.avi, cstate);
vc4_hdmi_write_infoframe(encoder, &frame); diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 18f6c700f6d0..144c495b99c4 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -401,8 +401,8 @@ drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame, const struct drm_display_mode *mode);
void -drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, - const struct drm_connector_state *conn_state); +drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame, + const struct drm_connector_state *conn_state);
void drm_hdmi_avi_infoframe_bars(struct hdmi_avi_infoframe *frame,
On Wed, Dec 15, 2021 at 01:43:54PM +0100, Maxime Ripard wrote:
The drm_hdmi_avi_infoframe_colorspace() function actually sets the colorimetry and extended_colorimetry fields in the hdmi_avi_infoframe structure with DRM_MODE_COLORIMETRY_* values.
To make things worse, the hdmi_avi_infoframe structure also has a colorspace field used to signal whether an RGB or YUV output is being used.
Let's remove the inconsistency and allow for the colorspace usage by renaming the function.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Ville Syrjälä ville.syrjala@linux.intel.com
Sadly we can't rename the property itself.
drivers/gpu/drm/drm_edid.c | 8 ++++---- drivers/gpu/drm/i915/display/intel_hdmi.c | 2 +- drivers/gpu/drm/i915/display/intel_lspcon.c | 2 +- drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- include/drm/drm_edid.h | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index e57d1b8cdaaa..dec7aecfc655 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -5890,13 +5890,13 @@ static const u32 hdmi_colorimetry_val[] = { #undef ACE
/**
- drm_hdmi_avi_infoframe_colorspace() - fill the HDMI AVI infoframe
colorspace information
- drm_hdmi_avi_infoframe_colorimetry() - fill the HDMI AVI infoframe
*/
colorimetry information
- @frame: HDMI AVI infoframe
- @conn_state: connector state
void -drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, +drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame, const struct drm_connector_state *conn_state) { u32 colorimetry_val; @@ -5915,7 +5915,7 @@ drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, frame->extended_colorimetry = (colorimetry_val >> 2) & EXTENDED_COLORIMETRY_MASK; } -EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorspace); +EXPORT_SYMBOL(drm_hdmi_avi_infoframe_colorimetry);
/**
- drm_hdmi_avi_infoframe_quant_range() - fill the HDMI AVI infoframe
diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.c b/drivers/gpu/drm/i915/display/intel_hdmi.c index 3b5b9e7b05b7..96e508ddc4af 100644 --- a/drivers/gpu/drm/i915/display/intel_hdmi.c +++ b/drivers/gpu/drm/i915/display/intel_hdmi.c @@ -730,7 +730,7 @@ intel_hdmi_compute_avi_infoframe(struct intel_encoder *encoder, else frame->colorspace = HDMI_COLORSPACE_RGB;
- drm_hdmi_avi_infoframe_colorspace(frame, conn_state);
drm_hdmi_avi_infoframe_colorimetry(frame, conn_state);
/* nonsense combination */ drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range &&
diff --git a/drivers/gpu/drm/i915/display/intel_lspcon.c b/drivers/gpu/drm/i915/display/intel_lspcon.c index 05d2d750fa53..092a925c6cf5 100644 --- a/drivers/gpu/drm/i915/display/intel_lspcon.c +++ b/drivers/gpu/drm/i915/display/intel_lspcon.c @@ -537,7 +537,7 @@ void lspcon_set_infoframes(struct intel_encoder *encoder, frame.avi.colorspace = HDMI_COLORSPACE_RGB;
/* Set the Colorspace as per the HDMI spec */
- drm_hdmi_avi_infoframe_colorspace(&frame.avi, conn_state);
drm_hdmi_avi_infoframe_colorimetry(&frame.avi, conn_state);
/* nonsense combination */ drm_WARN_ON(encoder->base.dev, crtc_state->limited_color_range &&
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 053fbaf765ca..be39e55ae113 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -502,7 +502,7 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) vc4_encoder->limited_rgb_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL);
- drm_hdmi_avi_infoframe_colorspace(&frame.avi, cstate);
drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate); drm_hdmi_avi_infoframe_bars(&frame.avi, cstate);
vc4_hdmi_write_infoframe(encoder, &frame);
diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 18f6c700f6d0..144c495b99c4 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -401,8 +401,8 @@ drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame, const struct drm_display_mode *mode);
void -drm_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
const struct drm_connector_state *conn_state);
+drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame,
const struct drm_connector_state *conn_state);
void drm_hdmi_avi_infoframe_bars(struct hdmi_avi_infoframe *frame, -- 2.33.1
We're going to need to tell whether we want to run with a full or limited range RGB output in multiple places in the code, so let's create a helper that will return whether we need with full range or not.
Acked-by: Thomas Zimmermann tzimmermann@suse.de Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index be39e55ae113..7966e3b00332 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -104,6 +104,15 @@ static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode) return (mode->clock * 1000) > 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; + + return !vc4_encoder->hdmi_monitor || + drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_FULL; +} + static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) { struct drm_info_node *node = (struct drm_info_node *)m->private; @@ -1119,8 +1128,7 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder,
mutex_lock(&vc4_hdmi->mutex);
- if (vc4_encoder->hdmi_monitor && - drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED) { + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { if (vc4_hdmi->variant->csc_setup) vc4_hdmi->variant->csc_setup(vc4_hdmi, true);
The CSC callbacks takes a boolean as an argument to tell whether we're using the full range or limited range RGB.
However, with the upcoming YUV support, the logic will be a bit more complex. In order to address this, let's make the callbacks take the entire mode, and call our new helper to tell whether the full or limited range RGB should be used.
Acked-by: Thomas Zimmermann tzimmermann@suse.de Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 31 +++++++++++-------------------- drivers/gpu/drm/vc4/vc4_hdmi.h | 4 ++-- 2 files changed, 13 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 7966e3b00332..47ff4507f017 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -490,7 +490,6 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); - struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); struct drm_connector *connector = &vc4_hdmi->connector; struct drm_connector_state *cstate = connector->state; const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; @@ -508,9 +507,9 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)
drm_hdmi_avi_infoframe_quant_range(&frame.avi, connector, mode, - vc4_encoder->limited_rgb_range ? - HDMI_QUANTIZATION_RANGE_LIMITED : - HDMI_QUANTIZATION_RANGE_FULL); + vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode) ? + HDMI_QUANTIZATION_RANGE_FULL : + HDMI_QUANTIZATION_RANGE_LIMITED); drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate); drm_hdmi_avi_infoframe_bars(&frame.avi, cstate);
@@ -735,7 +734,8 @@ static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) mutex_unlock(&vc4_hdmi->mutex); }
-static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) +static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + const struct drm_display_mode *mode) { unsigned long flags; u32 csc_ctl; @@ -745,7 +745,7 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR, VC4_HD_CSC_CTL_ORDER);
- if (enable) { + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { /* CEA VICs other than #1 requre limited range RGB * output unless overridden by an AVI infoframe. * Apply a colorspace conversion to squash 0-255 down @@ -775,7 +775,8 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); }
-static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) +static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + const struct drm_display_mode *mode) { unsigned long flags; u32 csc_ctl; @@ -784,7 +785,7 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable)
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
- if (enable) { + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { /* CEA VICs other than #1 requre limited range RGB * output unless overridden by an AVI infoframe. * Apply a colorspace conversion to squash 0-255 down @@ -1123,22 +1124,12 @@ static void vc4_hdmi_encoder_pre_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); unsigned long flags;
mutex_lock(&vc4_hdmi->mutex);
- if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { - if (vc4_hdmi->variant->csc_setup) - vc4_hdmi->variant->csc_setup(vc4_hdmi, true); - - vc4_encoder->limited_rgb_range = true; - } else { - if (vc4_hdmi->variant->csc_setup) - vc4_hdmi->variant->csc_setup(vc4_hdmi, false); - - vc4_encoder->limited_rgb_range = false; - } + if (vc4_hdmi->variant->csc_setup) + vc4_hdmi->variant->csc_setup(vc4_hdmi, mode);
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 36c0b082a43b..4a5536975bf6 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -12,7 +12,6 @@ struct vc4_hdmi_encoder { struct vc4_encoder base; bool hdmi_monitor; - bool limited_rgb_range; };
static inline struct vc4_hdmi_encoder * @@ -77,7 +76,8 @@ struct vc4_hdmi_variant { void (*reset)(struct vc4_hdmi *vc4_hdmi);
/* Callback to enable / disable the CSC */ - void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, bool enable); + void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, + const struct drm_display_mode *mode);
/* Callback to configure the video timings in the HDMI block */ void (*set_timings)(struct vc4_hdmi *vc4_hdmi,
On the BCM2711, the HDMI_VEC_INTERFACE_XBAR register configuration depends on whether we're using an RGB or YUV output. Let's move that configuration to the CSC setup.
Acked-by: Thomas Zimmermann tzimmermann@suse.de Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 47ff4507f017..0f8b1e907fae 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -785,6 +785,8 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+ HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { /* CEA VICs other than #1 requre limited range RGB * output unless overridden by an AVI infoframe. @@ -899,7 +901,6 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
- HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); HDMI_WRITE(HDMI_HORZA, (vsync_pos ? VC5_HDMI_HORZA_VPOS : 0) | (hsync_pos ? VC5_HDMI_HORZA_HPOS : 0) |
On BCM2711, the HDMI_CSC_CTL register value has been hardcoded to an opaque value. Let's replace it with properly defined values.
Acked-by: Thomas Zimmermann tzimmermann@suse.de Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 5 ++--- drivers/gpu/drm/vc4/vc4_regs.h | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 0f8b1e907fae..682c3c907cbe 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -779,9 +779,8 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, const struct drm_display_mode *mode) { unsigned long flags; - u32 csc_ctl; - - csc_ctl = 0x07; /* RGB_CONVERT_MODE = custom matrix, || USE_RGB_TO_YCBCR */ + 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);
diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index 489f921ef44d..952f2aad0785 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -774,6 +774,9 @@ enum { # define VC4_HD_CSC_CTL_RGB2YCC BIT(1) # define VC4_HD_CSC_CTL_ENABLE BIT(0)
+# define VC5_MT_CP_CSC_CTL_ENABLE BIT(2) +# define VC5_MT_CP_CSC_CTL_MODE_MASK VC4_MASK(1, 0) + # define VC4_DVP_HT_CLOCK_STOP_PIXEL BIT(1)
/* HVS display list information. */
The current CSC setup code for the BCM2711 uses a sequence of register writes to configure the CSC depending on whether we output using a full or limited range.
However, with the upcoming introduction of the YUV output, we're going to add new matrices to perform the conversions, so we should switch to something a bit more flexible that takes the matrix as an argument and programs the CSC accordingly.
Acked-by: Thomas Zimmermann tzimmermann@suse.de Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 79 +++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 29 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 682c3c907cbe..7fdb49e790f3 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -775,6 +775,52 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); }
+ +/* + * If we need to output Full Range RGB, then use the unity matrix + * + * [ 1 0 0 0] + * [ 0 1 0 0] + * [ 0 0 1 0] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_unity[3][4] = { + { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, 0x0000 }, +}; + +/* + * CEA VICs other than #1 require limited range RGB output unless + * overridden by an AVI infoframe. Apply a colorspace conversion to + * squash 0-255 down to 16-235. The matrix here is: + * + * [ 0.8594 0 0 16] + * [ 0 0.8594 0 16] + * [ 0 0 0.8594 16] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_to_limited_rgb[3][4] = { + { 0x1b80, 0x0000, 0x0000, 0x0400 }, + { 0x0000, 0x1b80, 0x0000, 0x0400 }, + { 0x0000, 0x0000, 0x1b80, 0x0400 }, +}; + +static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi, + const u16 coeffs[3][4]) +{ + lockdep_assert_held(&vc4_hdmi->hw_lock); + + HDMI_WRITE(HDMI_CSC_12_11, (coeffs[0][1] << 16) | coeffs[0][0]); + HDMI_WRITE(HDMI_CSC_14_13, (coeffs[0][3] << 16) | coeffs[0][2]); + HDMI_WRITE(HDMI_CSC_22_21, (coeffs[1][1] << 16) | coeffs[1][0]); + HDMI_WRITE(HDMI_CSC_24_23, (coeffs[1][3] << 16) | coeffs[1][2]); + HDMI_WRITE(HDMI_CSC_32_31, (coeffs[2][1] << 16) | coeffs[2][0]); + HDMI_WRITE(HDMI_CSC_34_33, (coeffs[2][3] << 16) | coeffs[2][2]); +} + static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, const struct drm_display_mode *mode) { @@ -786,35 +832,10 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021);
- if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) { - /* CEA VICs other than #1 requre limited range RGB - * output unless overridden by an AVI infoframe. - * Apply a colorspace conversion to squash 0-255 down - * to 16-235. The matrix here is: - * - * [ 0.8594 0 0 16] - * [ 0 0.8594 0 16] - * [ 0 0 0.8594 16] - * [ 0 0 0 1] - * Matrix is signed 2p13 fixed point, with signed 9p6 offsets - */ - HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x1b80); - HDMI_WRITE(HDMI_CSC_14_13, (0x0400 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_22_21, (0x1b80 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_24_23, (0x0400 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_34_33, (0x0400 << 16) | 0x1b80); - } else { - /* Still use the matrix for full range, but make it unity. - * Matrix is signed 2p13 fixed point, with signed 9p6 offsets - */ - HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x2000); - HDMI_WRITE(HDMI_CSC_14_13, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_22_21, (0x2000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_24_23, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); - HDMI_WRITE(HDMI_CSC_34_33, (0x0000 << 16) | 0x2000); - } + 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);
HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
In order to support the YUV output, we'll need the atomic state to know what is the state of the associated property in the CSC setup callback.
Let's change the prototype of that callback to allow us to access it.
Acked-by: Thomas Zimmermann tzimmermann@suse.de Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 7 ++++++- drivers/gpu/drm/vc4/vc4_hdmi.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 7fdb49e790f3..d79a70bae7f2 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -735,6 +735,7 @@ static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) }
static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *state, const struct drm_display_mode *mode) { unsigned long flags; @@ -822,6 +823,7 @@ static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi, }
static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *state, const struct drm_display_mode *mode) { unsigned long flags; @@ -1144,13 +1146,16 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_connector *connector = &vc4_hdmi->connector; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; + struct drm_connector_state *conn_state = + drm_atomic_get_new_connector_state(state, connector); unsigned long flags;
mutex_lock(&vc4_hdmi->mutex);
if (vc4_hdmi->variant->csc_setup) - vc4_hdmi->variant->csc_setup(vc4_hdmi, mode); + vc4_hdmi->variant->csc_setup(vc4_hdmi, conn_state, mode);
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 4a5536975bf6..2b6aaafc020a 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -77,6 +77,7 @@ struct vc4_hdmi_variant {
/* Callback to enable / disable the CSC */ void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, + struct drm_connector_state *state, const struct drm_display_mode *mode);
/* Callback to configure the video timings in the HDMI block */
Our code is doing the same clock rate validation in multiple instances. Let's create a helper to share the rate validation.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index d79a70bae7f2..8b6204df66bb 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -1262,6 +1262,19 @@ static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder, mutex_unlock(&vc4_hdmi->mutex); }
+static enum drm_mode_status +vc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi, + unsigned long long clock) +{ + 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; + + return MODE_OK; +} + #define WIFI_2_4GHz_CH1_MIN_FREQ 2400000000ULL #define WIFI_2_4GHz_CH1_MAX_FREQ 2422000000ULL
@@ -1305,10 +1318,7 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, 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)) + if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, pixel_rate) != MODE_OK) return -EINVAL;
vc4_state->pixel_rate = pixel_rate; @@ -1327,13 +1337,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->crtc_clock * 1000); }
static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {
The code to compute our clock rate for a given setup will be called in multiple places in the next patches, so let's create a separate function for it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 49 +++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 8b6204df66bb..02a5d5a8847e 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -1275,6 +1275,35 @@ vc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi, return MODE_OK; }
+static unsigned long long +vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode, + unsigned int bpc) +{ + unsigned long long clock = mode->crtc_clock * 1000; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + clock = clock * 2; + + return clock * bpc / 8; +} + +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 long long clock; + + clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc); + if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, clock) != MODE_OK) + return -EINVAL; + + vc4_state->pixel_rate = clock; + + return 0; +} + #define WIFI_2_4GHz_CH1_MIN_FREQ 2400000000ULL #define WIFI_2_4GHz_CH1_MAX_FREQ 2422000000ULL
@@ -1287,6 +1316,7 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); unsigned long long pixel_rate = mode->clock * 1000; unsigned long long tmds_rate; + int ret;
if (vc4_hdmi->variant->unsupported_odd_h_timings && ((mode->hdisplay % 2) || (mode->hsync_start % 2) || @@ -1307,21 +1337,10 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, 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); - } - - if (mode->flags & DRM_MODE_FLAG_DBLCLK) - pixel_rate = pixel_rate * 2; - - if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, pixel_rate) != MODE_OK) - return -EINVAL; - - vc4_state->pixel_rate = pixel_rate; + ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state, mode, + conn_state->max_bpc); + if (ret) + return ret;
return 0; }
In the function that validates that the clock isn't too high, we've only taken our controller limitations into account so far.
However, the sink can have a limit on the maximum TMDS clock it can deal with too which is exposed through the EDID and the drm_display_info.
Make sure we check it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 02a5d5a8847e..effc2d42e320 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -1266,12 +1266,18 @@ 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 (clock > (info->max_tmds_clock * 1000)) + return MODE_CLOCK_HIGH; + return MODE_OK; }
The current code only base its decision for whether the scrambler must be enabled or not on the pixel clock of the mode, but doesn't take the bits per color into account.
Let's leverage the new function to compute the clock rate in the scrambler setup code.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 17 +++++++++++++---- drivers/gpu/drm/vc4/vc4_hdmi.h | 5 +++++ 2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index effc2d42e320..be640b8be58c 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -99,9 +99,17 @@
#define HDMI_14_MAX_TMDS_CLK (340 * 1000 * 1000)
-static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode) + +static unsigned long long +vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode, + unsigned int bpc); + +static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode, + unsigned int bpc) { - return (mode->clock * 1000) > HDMI_14_MAX_TMDS_CLK; + unsigned long long clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc); + + return clock > HDMI_14_MAX_TMDS_CLK; }
static bool vc4_hdmi_is_full_range_rgb(struct vc4_hdmi *vc4_hdmi, @@ -272,7 +280,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)) { 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."); } @@ -613,7 +621,7 @@ 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)) return;
drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true); @@ -1256,6 +1264,7 @@ static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder, struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
mutex_lock(&vc4_hdmi->mutex); + vc4_hdmi->output_bpc = conn_state->max_bpc; memcpy(&vc4_hdmi->saved_adjusted_mode, &crtc_state->adjusted_mode, sizeof(vc4_hdmi->saved_adjusted_mode)); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 2b6aaafc020a..31b7d27deb8e 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -216,6 +216,11 @@ struct vc4_hdmi { * the scrambler on? Protected by @mutex. */ bool scdc_enabled; + + /** + * @output_bpc: BPC currently being used. Protected by @mutex. + */ + unsigned int output_bpc; };
static inline struct vc4_hdmi *
Currently we take the max_bpc property as the bpc value and do not try anything else.
However, what the other drivers seem to be doing is that they would try with the highest bpc allowed by the max_bpc property and the hardware capabilities, test if it results in an acceptable configuration, and if not decrease the bpc and try again.
Let's use the same logic.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 44 ++++++++++++++++++++++++++++++---- drivers/gpu/drm/vc4/vc4_hdmi.h | 4 +++- 2 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index be640b8be58c..b4aff3e0bba4 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -352,6 +352,7 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector) return NULL;
new_state->pixel_rate = vc4_state->pixel_rate; + new_state->output_bpc = vc4_state->output_bpc; __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);
return &new_state->base; @@ -907,6 +908,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; @@ -954,7 +957,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; @@ -1262,9 +1265,11 @@ 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); - vc4_hdmi->output_bpc = conn_state->max_bpc; + vc4_hdmi->output_bpc = vc4_state->output_bpc; memcpy(&vc4_hdmi->saved_adjusted_mode, &crtc_state->adjusted_mode, sizeof(vc4_hdmi->saved_adjusted_mode)); @@ -1319,6 +1324,38 @@ vc4_hdmi_encoder_compute_clock(const struct vc4_hdmi *vc4_hdmi, return 0; }
+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_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_clock(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, clock: %llu\n", + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode), + vc4_state->output_bpc, + vc4_state->pixel_rate); + + break; + } + + return ret; +} + #define WIFI_2_4GHz_CH1_MIN_FREQ 2400000000ULL #define WIFI_2_4GHz_CH1_MAX_FREQ 2422000000ULL
@@ -1352,8 +1389,7 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, pixel_rate = mode->clock * 1000; }
- ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state, mode, - conn_state->max_bpc); + ret = vc4_hdmi_encoder_compute_config(vc4_hdmi, vc4_state, mode); if (ret) return ret;
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 31b7d27deb8e..05d2b0eeaa9b 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -218,7 +218,8 @@ struct vc4_hdmi { bool scdc_enabled;
/** - * @output_bpc: BPC currently being used. Protected by @mutex. + * @output_bpc: Copy of @vc4_connector_state.output_bpc for use + * outside of KMS hooks. Protected by @mutex. */ unsigned int output_bpc; }; @@ -240,6 +241,7 @@ encoder_to_vc4_hdmi(struct drm_encoder *encoder) struct vc4_hdmi_connector_state { struct drm_connector_state base; unsigned long long pixel_rate; + unsigned int output_bpc; };
static inline struct vc4_hdmi_connector_state *
In addition to the RGB444 output, the BCM2711 HDMI controller supports the YUV444 and YUV422 output formats.
Let's add support for them in the driver, but still use RGB as the preferred format.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 286 ++++++++++++++++++++++++++-- drivers/gpu/drm/vc4/vc4_hdmi.h | 14 ++ drivers/gpu/drm/vc4/vc4_hdmi_regs.h | 6 + drivers/gpu/drm/vc4/vc4_regs.h | 16 ++ 4 files changed, 306 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index b4aff3e0bba4..5b41a03e5558 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -99,15 +99,30 @@
#define HDMI_14_MAX_TMDS_CLK (340 * 1000 * 1000)
+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); + 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) + unsigned int bpc, + enum vc4_hdmi_output_format fmt) { - unsigned long long clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc); + unsigned long long clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
return clock > HDMI_14_MAX_TMDS_CLK; } @@ -280,7 +295,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, 8)) { + 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."); } @@ -337,6 +352,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); }
@@ -353,6 +369,7 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector)
new_state->pixel_rate = vc4_state->pixel_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; @@ -496,11 +513,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; @@ -520,6 +564,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); @@ -622,7 +667,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, vc4_hdmi->output_bpc)) + 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); @@ -818,6 +865,38 @@ 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.212639 0.715169 0.072192 0 ] + * [ -0.117208 -0.394207 0.511416 128 ] + * [ 0.511416 -0.464524 -0.046891 128 ] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_to_full_yuv422_bt709[3][4] = { + { 0x06ce, 0x16e3, 0x024f, 0x0000 }, + { 0xfc41, 0xf364, 0x105e, 0x2000 }, + { 0x105e, 0xf124, 0xfe81, 0x2000 }, +}; + +/* + * Conversion between Full Range RGB and Full Range YUV444 using the + * BT.709 Colorspace + * + * [ -0.117208 -0.394207 0.511416 128 ] + * [ 0.511416 -0.464524 -0.046891 128 ] + * [ 0.212639 0.715169 0.072192 0 ] + * + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ +static const u16 vc5_hdmi_csc_full_rgb_to_full_yuv444_bt709[3][4] = { + { 0xfc41, 0xf364, 0x105e, 0x2000 }, + { 0x105e, 0xf124, 0xfe81, 0x2000 }, + { 0x06ce, 0x16e3, 0x024f, 0x0000 }, +}; + static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi, const u16 coeffs[3][4]) { @@ -835,19 +914,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_full_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_full_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); @@ -973,6 +1086,12 @@ 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); @@ -1270,12 +1389,97 @@ static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder,
mutex_lock(&vc4_hdmi->mutex); vc4_hdmi->output_bpc = vc4_state->output_bpc; + vc4_hdmi->output_format = vc4_state->output_format; memcpy(&vc4_hdmi->saved_adjusted_mode, &crtc_state->adjusted_mode, sizeof(vc4_hdmi->saved_adjusted_mode)); 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_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_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_YCRCB422)) { + 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_YCRCB444)) { + drm_dbg(dev, "Sink doesn't support YUV444.\n"); + return false; + } + + if (bpc == 10 && !(info->edid_hdmi_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_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) @@ -1297,13 +1501,17 @@ vc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi,
static unsigned long long vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode, - unsigned int bpc) + unsigned int bpc, + enum vc4_hdmi_output_format fmt) { unsigned long long clock = mode->crtc_clock * 1000;
if (mode->flags & DRM_MODE_FLAG_DBLCLK) clock = clock * 2;
+ if (fmt == VC4_HDMI_OUTPUT_YUV422) + bpc = 8; + return clock * bpc / 8; }
@@ -1311,11 +1519,11 @@ 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 bpc, unsigned int fmt) { unsigned long long clock;
- clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc); + clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt); if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, clock) != MODE_OK) return -EINVAL;
@@ -1324,11 +1532,56 @@ vc4_hdmi_encoder_compute_clock(const struct vc4_hdmi *vc4_hdmi, 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; @@ -1337,17 +1590,18 @@ vc4_hdmi_encoder_compute_config(const struct vc4_hdmi *vc4_hdmi, for (bpc = max_bpc; bpc >= 8; bpc -= 2) { drm_dbg(dev, "Trying with a %d bpc output\n", bpc);
- ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state, - mode, 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, clock: %llu\n", + "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->pixel_rate);
break; diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 05d2b0eeaa9b..f015ae80c5d4 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -117,6 +117,13 @@ struct vc4_hdmi_audio { bool streaming; };
+enum vc4_hdmi_output_format { + VC4_HDMI_OUTPUT_RGB, + VC4_HDMI_OUTPUT_YUV422, + VC4_HDMI_OUTPUT_YUV444, + VC4_HDMI_OUTPUT_YUV420, +}; + /* General HDMI hardware state. */ struct vc4_hdmi { struct vc4_hdmi_audio audio; @@ -222,6 +229,12 @@ struct vc4_hdmi { * outside of KMS hooks. Protected by @mutex. */ unsigned int output_bpc; + + /** + * @output_format: Copy of @vc4_connector_state.output_format + * for use outside of KMS hooks. Protected by @mutex. + */ + enum vc4_hdmi_output_format output_format; };
static inline struct vc4_hdmi * @@ -242,6 +255,7 @@ struct vc4_hdmi_connector_state { struct drm_connector_state base; unsigned long long pixel_rate; unsigned int output_bpc; + enum vc4_hdmi_output_format output_format; };
static inline struct vc4_hdmi_connector_state * diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h index fc971506bd4f..a040356b6bdc 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h @@ -54,6 +54,7 @@ enum vc4_hdmi_field { HDMI_CSC_24_23, HDMI_CSC_32_31, HDMI_CSC_34_33, + HDMI_CSC_CHANNEL_CTL, HDMI_CSC_CTL,
/* @@ -119,6 +120,7 @@ enum vc4_hdmi_field { HDMI_TX_PHY_POWERDOWN_CTL, HDMI_TX_PHY_RESET_CTL, HDMI_TX_PHY_TMDS_CLK_WORD_SEL, + HDMI_VEC_INTERFACE_CFG, HDMI_VEC_INTERFACE_XBAR, HDMI_VERTA0, HDMI_VERTA1, @@ -244,6 +246,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi0_fields[] = { VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1c4),
VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc), + VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0ec), VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0),
VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000), @@ -289,6 +292,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi0_fields[] = { VC5_CSC_REG(HDMI_CSC_24_23, 0x010), VC5_CSC_REG(HDMI_CSC_32_31, 0x014), VC5_CSC_REG(HDMI_CSC_34_33, 0x018), + VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c), };
static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = { @@ -324,6 +328,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = { VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1c4),
VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc), + VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0ec), VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0),
VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000), @@ -369,6 +374,7 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = { VC5_CSC_REG(HDMI_CSC_24_23, 0x010), VC5_CSC_REG(HDMI_CSC_32_31, 0x014), VC5_CSC_REG(HDMI_CSC_34_33, 0x018), + VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c), };
static inline diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index 952f2aad0785..392b0be053f8 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -774,11 +774,27 @@ enum { # define VC4_HD_CSC_CTL_RGB2YCC BIT(1) # define VC4_HD_CSC_CTL_ENABLE BIT(0)
+# define VC5_MT_CP_CSC_CTL_USE_444_TO_422 BIT(6) +# define VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_MASK \ + VC4_MASK(5, 4) +# define VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_STANDARD \ + 3 +# define VC5_MT_CP_CSC_CTL_USE_RNG_SUPPRESSION BIT(3) # define VC5_MT_CP_CSC_CTL_ENABLE BIT(2) # define VC5_MT_CP_CSC_CTL_MODE_MASK VC4_MASK(1, 0)
+# define VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_MASK \ + VC4_MASK(7, 6) +# define VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_LEGACY_STYLE \ + 2 + # define VC4_DVP_HT_CLOCK_STOP_PIXEL BIT(1)
+# define VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_MASK \ + VC4_MASK(3, 2) +# define VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_FORMAT_422_LEGACY \ + 2 + /* HVS display list information. */ #define HVS_BOOTLOADER_DLIST_END 32
dri-devel@lists.freedesktop.org