Re: [PATCH v2 3/9] drm/vc4: hdmi: Add Broadcast RGB property to allow override of RGB range

From: Hans Verkuil
Date: Tue Feb 21 2023 - 06:39:42 EST


On 2/20/23 16:23, Dave Stevenson wrote:
> Hi Hans
>
> On Sat, 18 Feb 2023 at 11:33, Hans Verkuil <hverkuil@xxxxxxxxx> wrote:
>>
>> Hi Maxime, Dave,
>>
>> On 26/01/2023 14:46, Maxime Ripard wrote:
>>> From: Dave Stevenson <dave.stevenson@xxxxxxxxxxxxxxx>
>>>
>>> Copy Intel's "Broadcast RGB" property semantics to add manual override
>>> of the HDMI pixel range for monitors that don't abide by the content
>>> of the AVI Infoframe.
>>
>> Do we have to copy that property as-is?
>
> Firstly I'll agree with your later comment that having this control
> allows testing of a range of output modes, and working around HDMI
> sinks that have dodgy implementations.
> (In our vendor kernel we now also have a property to override the
> kernel chosen output format to enable testing of YCbCr4:4:4 and 4:2:2
> output).
>
> The DRM subsystem has the requirement for an open-source userspace
> project to be using any new property before it will be merged [1].
> This property already exists for i915 and gma-500, therefore avoids
> that requirement.
>
> There are objections to the UAPI for Broadcast RGB [2], but if it's
> good enough for the existing implementations then there can be no
> objection to it being implemented in the same way for other drivers.
> Otherwise it is a missing feature of the DRM API, and the linked
> discussion is realistically at least a year away from being resolved.
> Why bury our heads in the sand for that period?
>
> [1] https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#open-source-userspace-requirements
> [2] https://lists.freedesktop.org/archives/dri-devel/2023-February/391061.html
>
>> First of all, I think this should really be a drm-level property, rather than
>> a driver property: RGB Quantization Range mismatches are the bane of my life,
>> and I think a way to override this would help everyone.
>
> Linked to above, if it were the preferred method for controlling this
> then I would expect it to become a drm-level property.
>
>> Secondly, I hate the name they came up with: 'Broadcast RGB' is pretty meaningless.
>> Can't we stick to something closer to what the CTA-861/HDMI specs use, which is
>> 'RGB Quantization Range'? So either use that, or just 'RGB Range'.
>>
>> In addition, 'Limited 16:235' should just be 'Limited' since the actual range
>> depends on the bits-per-color-component.
>
> It's documented UAPI with those names[3], therefore any change would
> be a change to user-space's expectation and a regression. At least by
> sticking with the same names then any user space implementation that
> exists for i915 or gma-500 will also work in the same way on vc4.
>
> [3] https://www.kernel.org/doc/html/latest/gpu/drm-kms.html#existing-kms-properties

Thank you for linking to these threads. It's a terrible name, but it is still
better to have this property than to not have it :-)

>
>>>
>>> Signed-off-by: Dave Stevenson <dave.stevenson@xxxxxxxxxxxxxxx>
>>> Signed-off-by: Maxime Ripard <maxime@xxxxxxxxxx>
>>> ---
>>> drivers/gpu/drm/vc4/vc4_hdmi.c | 97 ++++++++++++++++++++++++++++++++++++++++--
>>> drivers/gpu/drm/vc4/vc4_hdmi.h | 9 ++++
>>> 2 files changed, 102 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
>>> index 4b3bf77bb5cd..78749c6fa837 100644
>>> --- a/drivers/gpu/drm/vc4/vc4_hdmi.c
>>> +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
>>> @@ -150,10 +150,16 @@ static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode,
>>> }
>>>
>>> static bool vc4_hdmi_is_full_range_rgb(struct vc4_hdmi *vc4_hdmi,
>>> - const struct drm_display_mode *mode)
>>> + struct vc4_hdmi_connector_state *vc4_state)
>>> {
>>> + const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
>>> struct drm_display_info *display = &vc4_hdmi->connector.display_info;
>>>
>>> + if (vc4_state->broadcast_rgb == VC4_HDMI_BROADCAST_RGB_LIMITED)
>>> + return false;
>>> + else if (vc4_state->broadcast_rgb == VC4_HDMI_BROADCAST_RGB_FULL)
>>> + return true;
>>> +
>>> return !display->is_hdmi ||
>>> drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_FULL;
>>> }
>>> @@ -524,8 +530,12 @@ static int vc4_hdmi_connector_atomic_check(struct drm_connector *connector,
>>> {
>>> struct drm_connector_state *old_state =
>>> drm_atomic_get_old_connector_state(state, connector);
>>> + struct vc4_hdmi_connector_state *old_vc4_state =
>>> + conn_state_to_vc4_hdmi_conn_state(old_state);
>>> struct drm_connector_state *new_state =
>>> drm_atomic_get_new_connector_state(state, connector);
>>> + struct vc4_hdmi_connector_state *new_vc4_state =
>>> + conn_state_to_vc4_hdmi_conn_state(new_state);
>>> struct drm_crtc *crtc = new_state->crtc;
>>>
>>> if (!crtc)
>>> @@ -558,6 +568,7 @@ static int vc4_hdmi_connector_atomic_check(struct drm_connector *connector,
>>> }
>>>
>>> if (old_state->colorspace != new_state->colorspace ||
>>> + old_vc4_state->broadcast_rgb != new_vc4_state->broadcast_rgb ||
>>
>> The problem with this is that this will cause a mode change, even though all
>> that is necessary is to update the csc matrix and AVI InfoFrame.
>>
>> I used this code (added just before the 'return 0;' at the end of this function):
>>
>> if (old_vc4_state->broadcast_rgb != new_vc4_state->broadcast_rgb) {
>> const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
>>
>> old_vc4_state->broadcast_rgb = new_vc4_state->broadcast_rgb;
>> vc4_hdmi_set_avi_infoframe(encoder);
>> if (vc4_hdmi->variant->csc_setup)
>> vc4_hdmi->variant->csc_setup(vc4_hdmi, old_state, mode);
>> }
>>
>> I'm certain this is in the wrong place, but I'm not familiar enough with the drm API
>> to determine where this should go.
>
> atomic_check is meant to validate the state, but must never touch the
> hardware [4].
> atomic_commit updates the hardware, although for struct
> drm_connector_helper_funcs atomic_commit is documented as only being
> applicable for writeback connectors [5]. Hence our only sensible
> options are to trigger a mode change which reprograms everything, or
> ignore the helpers.
> There may be scope for improving this, but it'll need significant
> driver (and possibly framework) rework in order to be done properly.
>
> [4] https://elixir.bootlin.com/linux/latest/source/include/drm/drm_modeset_helper_vtables.h#L1083
> [5] https://elixir.bootlin.com/linux/latest/source/include/drm/drm_modeset_helper_vtables.h#L1101
>
>> This approach probably applies to the hdr_metadata metadata as well, that too
>> doesn't need a mode change.
>>
>> I see that the i915 driver has a 'fastset' mechanism for changes like this, but
>> it is not clear to me how that interacts with the drm API.
>>
>> I've been playing around with this vc4 driver and it is proving to be very useful
>> for debugging all sorts of quantization range bugs in other equipment.
>
> Glad it's proving useful.
>
> We're also needing it as users have misbehaving monitors, and telling
> them to replace the monitor as it breaks the HDMI spec generally
> doesn't go down well! Likewise I've had users needing YCbCr444 output
> because of some limitation in their receiving device, hence overriding
> the kernel choice of output mode. It'd be nice if there were consensus
> on the correct way to achieve that.

In any case, given this background you can add my:

Reviewed-by: Hans Verkuil <hverkuil-cisco@xxxxxxxxx>

Regards,

Hans

>
> Cheers.
> Dave
>
>> Regards,
>>
>> Hans
>>
>>> !drm_connector_atomic_hdr_metadata_equal(old_state, new_state)) {
>>> struct drm_crtc_state *crtc_state;
>>>
>>> @@ -571,6 +582,49 @@ static int vc4_hdmi_connector_atomic_check(struct drm_connector *connector,
>>> return 0;
>>> }
>>>
>>> +static int vc4_hdmi_connector_get_property(struct drm_connector *connector,
>>> + const struct drm_connector_state *state,
>>> + struct drm_property *property,
>>> + uint64_t *val)
>>> +{
>>> + struct drm_device *drm = connector->dev;
>>> + struct vc4_hdmi *vc4_hdmi =
>>> + connector_to_vc4_hdmi(connector);
>>> + const struct vc4_hdmi_connector_state *vc4_conn_state =
>>> + conn_state_to_vc4_hdmi_conn_state(state);
>>> +
>>> + if (property == vc4_hdmi->broadcast_rgb_property) {
>>> + *val = vc4_conn_state->broadcast_rgb;
>>> + } else {
>>> + drm_dbg(drm, "Unknown property [PROP:%d:%s]\n",
>>> + property->base.id, property->name);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int vc4_hdmi_connector_set_property(struct drm_connector *connector,
>>> + struct drm_connector_state *state,
>>> + struct drm_property *property,
>>> + uint64_t val)
>>> +{
>>> + struct drm_device *drm = connector->dev;
>>> + struct vc4_hdmi *vc4_hdmi =
>>> + connector_to_vc4_hdmi(connector);
>>> + struct vc4_hdmi_connector_state *vc4_conn_state =
>>> + conn_state_to_vc4_hdmi_conn_state(state);
>>> +
>>> + if (property == vc4_hdmi->broadcast_rgb_property) {
>>> + vc4_conn_state->broadcast_rgb = val;
>>> + return 0;
>>> + }
>>> +
>>> + drm_dbg(drm, "Unknown property [PROP:%d:%s]\n",
>>> + property->base.id, property->name);
>>> + return -EINVAL;
>>> +}
>>> +
>>> static void vc4_hdmi_connector_reset(struct drm_connector *connector)
>>> {
>>> struct vc4_hdmi_connector_state *old_state =
>>> @@ -590,6 +644,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;
>>> + new_state->broadcast_rgb = VC4_HDMI_BROADCAST_RGB_AUTO;
>>> drm_atomic_helper_connector_tv_margins_reset(connector);
>>> }
>>>
>>> @@ -607,6 +662,7 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector)
>>> 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;
>>> + new_state->broadcast_rgb = vc4_state->broadcast_rgb;
>>> __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);
>>>
>>> return &new_state->base;
>>> @@ -617,6 +673,8 @@ static const struct drm_connector_funcs vc4_hdmi_connector_funcs = {
>>> .reset = vc4_hdmi_connector_reset,
>>> .atomic_duplicate_state = vc4_hdmi_connector_duplicate_state,
>>> .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>>> + .atomic_get_property = vc4_hdmi_connector_get_property,
>>> + .atomic_set_property = vc4_hdmi_connector_set_property,
>>> };
>>>
>>> static const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs = {
>>> @@ -625,6 +683,33 @@ static const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs =
>>> .atomic_check = vc4_hdmi_connector_atomic_check,
>>> };
>>>
>>> +static const struct drm_prop_enum_list broadcast_rgb_names[] = {
>>> + { VC4_HDMI_BROADCAST_RGB_AUTO, "Automatic" },
>>> + { VC4_HDMI_BROADCAST_RGB_FULL, "Full" },
>>> + { VC4_HDMI_BROADCAST_RGB_LIMITED, "Limited 16:235" },
>>> +};
>>> +
>>> +static void
>>> +vc4_hdmi_attach_broadcast_rgb_property(struct drm_device *dev,
>>> + struct vc4_hdmi *vc4_hdmi)
>>> +{
>>> + struct drm_property *prop = vc4_hdmi->broadcast_rgb_property;
>>> +
>>> + if (!prop) {
>>> + prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM,
>>> + "Broadcast RGB",
>>> + broadcast_rgb_names,
>>> + ARRAY_SIZE(broadcast_rgb_names));
>>> + if (!prop)
>>> + return;
>>> +
>>> + vc4_hdmi->broadcast_rgb_property = prop;
>>> + }
>>> +
>>> + drm_object_attach_property(&vc4_hdmi->connector.base, prop,
>>> + VC4_HDMI_BROADCAST_RGB_AUTO);
>>> +}
>>> +
>>> static int vc4_hdmi_connector_init(struct drm_device *dev,
>>> struct vc4_hdmi *vc4_hdmi)
>>> {
>>> @@ -671,6 +756,8 @@ static int vc4_hdmi_connector_init(struct drm_device *dev,
>>> if (vc4_hdmi->variant->supports_hdr)
>>> drm_connector_attach_hdr_output_metadata_property(connector);
>>>
>>> + vc4_hdmi_attach_broadcast_rgb_property(dev, vc4_hdmi);
>>> +
>>> drm_connector_attach_encoder(connector, encoder);
>>>
>>> return 0;
>>> @@ -825,7 +912,7 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)
>>>
>>> drm_hdmi_avi_infoframe_quant_range(&frame.avi,
>>> connector, mode,
>>> - vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode) ?
>>> + vc4_hdmi_is_full_range_rgb(vc4_hdmi, vc4_state) ?
>>> HDMI_QUANTIZATION_RANGE_FULL :
>>> HDMI_QUANTIZATION_RANGE_LIMITED);
>>> drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate);
>>> @@ -1066,6 +1153,8 @@ static void vc4_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);
>>> struct drm_device *drm = vc4_hdmi->connector.dev;
>>> unsigned long flags;
>>> u32 csc_ctl;
>>> @@ -1079,7 +1168,7 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
>>> csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR,
>>> VC4_HD_CSC_CTL_ORDER);
>>>
>>> - if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode)) {
>>> + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, vc4_state)) {
>>> /* 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
>>> @@ -1232,7 +1321,7 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
>>> case VC4_HDMI_OUTPUT_RGB:
>>> if_xbar = 0x354021;
>>>
>>> - if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode))
>>> + if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, vc4_state))
>>> 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);
>>> diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h
>>> index 5d249ac54cd1..89800c48aa24 100644
>>> --- a/drivers/gpu/drm/vc4/vc4_hdmi.h
>>> +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h
>>> @@ -117,6 +117,12 @@ enum vc4_hdmi_output_format {
>>> VC4_HDMI_OUTPUT_YUV420,
>>> };
>>>
>>> +enum vc4_hdmi_broadcast_rgb {
>>> + VC4_HDMI_BROADCAST_RGB_AUTO,
>>> + VC4_HDMI_BROADCAST_RGB_FULL,
>>> + VC4_HDMI_BROADCAST_RGB_LIMITED,
>>> +};
>>> +
>>> /* General HDMI hardware state. */
>>> struct vc4_hdmi {
>>> struct vc4_hdmi_audio audio;
>>> @@ -129,6 +135,8 @@ struct vc4_hdmi {
>>>
>>> struct delayed_work scrambling_work;
>>>
>>> + struct drm_property *broadcast_rgb_property;
>>> +
>>> struct i2c_adapter *ddc;
>>> void __iomem *hdmicore_regs;
>>> void __iomem *hd_regs;
>>> @@ -238,6 +246,7 @@ struct vc4_hdmi_connector_state {
>>> unsigned long long tmds_char_rate;
>>> unsigned int output_bpc;
>>> enum vc4_hdmi_output_format output_format;
>>> + enum vc4_hdmi_broadcast_rgb broadcast_rgb;
>>> };
>>>
>>> #define conn_state_to_vc4_hdmi_conn_state(_state) \
>>>
>>