Unverified Commit f378b772 authored by Maxime Ripard's avatar Maxime Ripard
Browse files

drm/connector: hdmi: Add Infoframes generation



Infoframes in KMS is usually handled by a bunch of low-level helpers
that require quite some boilerplate for drivers. This leads to
discrepancies with how drivers generate them, and which are actually
sent.

Now that we have everything needed to generate them in the HDMI
connector state, we can generate them in our common logic so that
drivers can simply reuse what we precomputed.

Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: default avatarDmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-22-c5af16c3aae2@kernel.org


Signed-off-by: default avatarMaxime Ripard <mripard@kernel.org>
parent 4af70f19
Loading
Loading
Loading
Loading
+336 −0
Original line number Diff line number Diff line
@@ -333,6 +333,144 @@ hdmi_compute_config(const struct drm_connector *connector,
	return -EINVAL;
}

static int hdmi_generate_avi_infoframe(const struct drm_connector *connector,
				       struct drm_connector_state *conn_state)
{
	const struct drm_display_mode *mode =
		connector_state_get_mode(conn_state);
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.avi;
	struct hdmi_avi_infoframe *frame =
		&infoframe->data.avi;
	bool is_limited_range = conn_state->hdmi.is_limited_range;
	enum hdmi_quantization_range rgb_quant_range =
		is_limited_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL;
	int ret;

	ret = drm_hdmi_avi_infoframe_from_display_mode(frame, connector, mode);
	if (ret)
		return ret;

	frame->colorspace = conn_state->hdmi.output_format;

	/*
	 * FIXME: drm_hdmi_avi_infoframe_quant_range() doesn't handle
	 * YUV formats at all at the moment, so if we ever support YUV
	 * formats this needs to be revised.
	 */
	drm_hdmi_avi_infoframe_quant_range(frame, connector, mode, rgb_quant_range);
	drm_hdmi_avi_infoframe_colorimetry(frame, conn_state);
	drm_hdmi_avi_infoframe_bars(frame, conn_state);

	infoframe->set = true;

	return 0;
}

static int hdmi_generate_spd_infoframe(const struct drm_connector *connector,
				       struct drm_connector_state *conn_state)
{
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.spd;
	struct hdmi_spd_infoframe *frame =
		&infoframe->data.spd;
	int ret;

	ret = hdmi_spd_infoframe_init(frame,
				      connector->hdmi.vendor,
				      connector->hdmi.product);
	if (ret)
		return ret;

	frame->sdi = HDMI_SPD_SDI_PC;

	infoframe->set = true;

	return 0;
}

static int hdmi_generate_hdr_infoframe(const struct drm_connector *connector,
				       struct drm_connector_state *conn_state)
{
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.hdr_drm;
	struct hdmi_drm_infoframe *frame =
		&infoframe->data.drm;
	int ret;

	if (connector->max_bpc < 10)
		return 0;

	if (!conn_state->hdr_output_metadata)
		return 0;

	ret = drm_hdmi_infoframe_set_hdr_metadata(frame, conn_state);
	if (ret)
		return ret;

	infoframe->set = true;

	return 0;
}

static int hdmi_generate_hdmi_vendor_infoframe(const struct drm_connector *connector,
					       struct drm_connector_state *conn_state)
{
	const struct drm_display_info *info = &connector->display_info;
	const struct drm_display_mode *mode =
		connector_state_get_mode(conn_state);
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.hdmi;
	struct hdmi_vendor_infoframe *frame =
		&infoframe->data.vendor.hdmi;
	int ret;

	if (!info->has_hdmi_infoframe)
		return 0;

	ret = drm_hdmi_vendor_infoframe_from_display_mode(frame, connector, mode);
	if (ret)
		return ret;

	infoframe->set = true;

	return 0;
}

static int
hdmi_generate_infoframes(const struct drm_connector *connector,
			 struct drm_connector_state *conn_state)
{
	const struct drm_display_info *info = &connector->display_info;
	int ret;

	if (!info->is_hdmi)
		return 0;

	ret = hdmi_generate_avi_infoframe(connector, conn_state);
	if (ret)
		return ret;

	ret = hdmi_generate_spd_infoframe(connector, conn_state);
	if (ret)
		return ret;

	/*
	 * Audio Infoframes will be generated by ALSA, and updated by
	 * drm_atomic_helper_connector_hdmi_update_audio_infoframe().
	 */

	ret = hdmi_generate_hdr_infoframe(connector, conn_state);
	if (ret)
		return ret;

	ret = hdmi_generate_hdmi_vendor_infoframe(connector, conn_state);
	if (ret)
		return ret;

	return 0;
}

/**
 * drm_atomic_helper_connector_hdmi_check() - Helper to check HDMI connector atomic state
 * @connector: DRM Connector
@@ -362,6 +500,10 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
	if (ret)
		return ret;

	ret = hdmi_generate_infoframes(connector, new_conn_state);
	if (ret)
		return ret;

	if (old_conn_state->hdmi.broadcast_rgb != new_conn_state->hdmi.broadcast_rgb ||
	    old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc ||
	    old_conn_state->hdmi.output_format != new_conn_state->hdmi.output_format) {
@@ -378,3 +520,197 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
	return 0;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_check);

#define HDMI_MAX_INFOFRAME_SIZE		29

static int clear_device_infoframe(struct drm_connector *connector,
				  enum hdmi_infoframe_type type)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_device *dev = connector->dev;
	int ret;

	drm_dbg_kms(dev, "Clearing infoframe type 0x%x\n", type);

	if (!funcs || !funcs->clear_infoframe) {
		drm_dbg_kms(dev, "Function not implemented, bailing.\n");
		return 0;
	}

	ret = funcs->clear_infoframe(connector, type);
	if (ret) {
		drm_dbg_kms(dev, "Call failed: %d\n", ret);
		return ret;
	}

	return 0;
}

static int clear_infoframe(struct drm_connector *connector,
			   struct drm_connector_hdmi_infoframe *old_frame)
{
	int ret;

	ret = clear_device_infoframe(connector, old_frame->data.any.type);
	if (ret)
		return ret;

	return 0;
}

static int write_device_infoframe(struct drm_connector *connector,
				  union hdmi_infoframe *frame)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_device *dev = connector->dev;
	u8 buffer[HDMI_MAX_INFOFRAME_SIZE];
	int ret;
	int len;

	drm_dbg_kms(dev, "Writing infoframe type %x\n", frame->any.type);

	if (!funcs || !funcs->write_infoframe) {
		drm_dbg_kms(dev, "Function not implemented, bailing.\n");
		return -EINVAL;
	}

	len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer));
	if (len < 0)
		return len;

	ret = funcs->write_infoframe(connector, frame->any.type, buffer, len);
	if (ret) {
		drm_dbg_kms(dev, "Call failed: %d\n", ret);
		return ret;
	}

	return 0;
}

static int write_infoframe(struct drm_connector *connector,
			   struct drm_connector_hdmi_infoframe *new_frame)
{
	int ret;

	ret = write_device_infoframe(connector, &new_frame->data);
	if (ret)
		return ret;

	return 0;
}

static int write_or_clear_infoframe(struct drm_connector *connector,
				    struct drm_connector_hdmi_infoframe *old_frame,
				    struct drm_connector_hdmi_infoframe *new_frame)
{
	if (new_frame->set)
		return write_infoframe(connector, new_frame);

	if (old_frame->set && !new_frame->set)
		return clear_infoframe(connector, old_frame);

	return 0;
}

/**
 * drm_atomic_helper_connector_hdmi_update_infoframes - Update the Infoframes
 * @connector: A pointer to the HDMI connector
 * @state: The HDMI connector state to generate the infoframe from
 *
 * This function is meant for HDMI connector drivers to write their
 * infoframes. It will typically be used in a
 * @drm_connector_helper_funcs.atomic_enable implementation.
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector,
						       struct drm_atomic_state *state)
{
	struct drm_connector_state *old_conn_state =
		drm_atomic_get_old_connector_state(state, connector);
	struct drm_connector_state *new_conn_state =
		drm_atomic_get_new_connector_state(state, connector);
	struct drm_display_info *info = &connector->display_info;
	int ret;

	if (!info->is_hdmi)
		return 0;

	mutex_lock(&connector->hdmi.infoframes.lock);

	ret = write_or_clear_infoframe(connector,
				       &old_conn_state->hdmi.infoframes.avi,
				       &new_conn_state->hdmi.infoframes.avi);
	if (ret)
		goto out;

	if (connector->hdmi.infoframes.audio.set) {
		ret = write_infoframe(connector,
				      &connector->hdmi.infoframes.audio);
		if (ret)
			goto out;
	}

	ret = write_or_clear_infoframe(connector,
				       &old_conn_state->hdmi.infoframes.hdr_drm,
				       &new_conn_state->hdmi.infoframes.hdr_drm);
	if (ret)
		goto out;

	ret = write_or_clear_infoframe(connector,
				       &old_conn_state->hdmi.infoframes.spd,
				       &new_conn_state->hdmi.infoframes.spd);
	if (ret)
		goto out;

	if (info->has_hdmi_infoframe) {
		ret = write_or_clear_infoframe(connector,
					       &old_conn_state->hdmi.infoframes.hdmi,
					       &new_conn_state->hdmi.infoframes.hdmi);
		if (ret)
			goto out;
	}

out:
	mutex_unlock(&connector->hdmi.infoframes.lock);
	return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_infoframes);

/**
 * drm_atomic_helper_connector_hdmi_update_audio_infoframe - Update the Audio Infoframe
 * @connector: A pointer to the HDMI connector
 * @frame: A pointer to the audio infoframe to write
 *
 * This function is meant for HDMI connector drivers to update their
 * audio infoframe. It will typically be used in one of the ALSA hooks
 * (most likely prepare).
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int
drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector,
							struct hdmi_audio_infoframe *frame)
{
	struct drm_connector_hdmi_infoframe *infoframe =
		&connector->hdmi.infoframes.audio;
	struct drm_display_info *info = &connector->display_info;
	int ret;

	if (!info->is_hdmi)
		return 0;

	mutex_lock(&connector->hdmi.infoframes.lock);

	memcpy(&infoframe->data, frame, sizeof(infoframe->data));
	infoframe->set = true;

	ret = write_infoframe(connector, infoframe);

	mutex_unlock(&connector->hdmi.infoframes.lock);

	return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_audio_infoframe);
+14 −0
Original line number Diff line number Diff line
@@ -278,6 +278,7 @@ static int __drm_connector_init(struct drm_device *dev,
	INIT_LIST_HEAD(&connector->modes);
	mutex_init(&connector->mutex);
	mutex_init(&connector->edid_override_mutex);
	mutex_init(&connector->hdmi.infoframes.lock);
	connector->edid_blob_ptr = NULL;
	connector->epoch_counter = 0;
	connector->tile_blob_ptr = NULL;
@@ -456,6 +457,8 @@ EXPORT_SYMBOL(drmm_connector_init);
 * drmm_connector_hdmi_init - Init a preallocated HDMI connector
 * @dev: DRM device
 * @connector: A pointer to the HDMI connector to init
 * @vendor: HDMI Controller Vendor name
 * @product: HDMI Controller Product name
 * @funcs: callbacks for this connector
 * @hdmi_funcs: HDMI-related callbacks for this connector
 * @connector_type: user visible type of the connector
@@ -476,6 +479,7 @@ EXPORT_SYMBOL(drmm_connector_init);
 */
int drmm_connector_hdmi_init(struct drm_device *dev,
			     struct drm_connector *connector,
			     const char *vendor, const char *product,
			     const struct drm_connector_funcs *funcs,
			     const struct drm_connector_hdmi_funcs *hdmi_funcs,
			     int connector_type,
@@ -485,6 +489,13 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
{
	int ret;

	if (!vendor || !product)
		return -EINVAL;

	if ((strlen(vendor) > DRM_CONNECTOR_HDMI_VENDOR_LEN) ||
	    (strlen(product) > DRM_CONNECTOR_HDMI_PRODUCT_LEN))
		return -EINVAL;

	if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA ||
	      connector_type == DRM_MODE_CONNECTOR_HDMIB))
		return -EINVAL;
@@ -500,6 +511,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
		return ret;

	connector->hdmi.supported_formats = supported_formats;
	strtomem_pad(connector->hdmi.vendor, vendor, 0);
	strtomem_pad(connector->hdmi.product, product, 0);

	/*
	 * drm_connector_attach_max_bpc_property() requires the
@@ -652,6 +665,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
		connector->funcs->atomic_destroy_state(connector,
						       connector->state);

	mutex_destroy(&connector->hdmi.infoframes.lock);
	mutex_destroy(&connector->mutex);

	memset(connector, 0, sizeof(*connector));
+12 −0
Original line number Diff line number Diff line
@@ -191,6 +191,7 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -210,6 +211,7 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -229,6 +231,7 @@ static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -248,6 +251,7 @@ static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -272,6 +276,7 @@ static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -312,6 +317,7 @@ static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -352,6 +358,7 @@ static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -387,6 +394,7 @@ static void drm_test_connector_hdmi_init_formats_empty(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -406,6 +414,7 @@ static void drm_test_connector_hdmi_init_formats_no_rgb(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
@@ -426,6 +435,7 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       connector_type,
@@ -460,6 +470,7 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test)
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       connector_type,
@@ -725,6 +736,7 @@ static void drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector(
	int ret;

	ret = drmm_connector_hdmi_init(&priv->drm, connector,
				       "Vendor", "Product",
				       &dummy_funcs,
				       &dummy_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
+1 −0
Original line number Diff line number Diff line
@@ -206,6 +206,7 @@ drm_atomic_helper_connector_hdmi_init(struct kunit *test,

	conn = &priv->connector;
	ret = drmm_connector_hdmi_init(drm, conn,
				       "Vendor", "Product",
				       &dummy_connector_funcs,
				       &dummy_connector_hdmi_funcs,
				       DRM_MODE_CONNECTOR_HDMIA,
+6 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
struct drm_atomic_state;
struct drm_connector;
struct drm_connector_state;
struct hdmi_audio_infoframe;

void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
					      struct drm_connector_state *new_conn_state);
@@ -13,4 +14,9 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
					   struct drm_atomic_state *state);

int drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector,
							    struct hdmi_audio_infoframe *frame);
int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector,
						       struct drm_atomic_state *state);

#endif // DRM_HDMI_STATE_HELPER_H_
Loading