Commit e802c783 authored by Dmitry Baryshkov's avatar Dmitry Baryshkov
Browse files

drm/display: hdmi_state_helper: split InfoFrame functions per type



Havign a single set of InfoFrame callbacks doesn't provide enough
information to the DRM framework about the InfoFrame types that are
actually supported. Also it's not really future-proof: it provides a way
to program only a single Vendor-Specific frame, however we might need to
support multiple VSIs at the same time (e.g. HDMI vs HDMI Forum
VSIs).

Provide separate sets of callbacks, one per the InfoFrame type.

Acked-by: default avatarMaxime Ripard <mripard@kernel.org>
Link: https://patch.msgid.link/20260107-limit-infoframes-2-v4-6-213d0d3bd490@oss.qualcomm.com


Signed-off-by: default avatarDmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
parent b626b1a1
Loading
Loading
Loading
Loading
+157 −49
Original line number Diff line number Diff line
@@ -401,8 +401,7 @@ drm_bridge_connector_tmds_char_rate_valid(const struct drm_connector *connector,
		return MODE_OK;
}

static int drm_bridge_connector_clear_infoframe(struct drm_connector *connector,
						enum hdmi_infoframe_type type)
static int drm_bridge_connector_clear_avi_infoframe(struct drm_connector *connector)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
@@ -412,34 +411,69 @@ static int drm_bridge_connector_clear_infoframe(struct drm_connector *connector,
	if (!bridge)
		return -EINVAL;

	switch (type) {
	case HDMI_INFOFRAME_TYPE_AVI:
		/* required */
	return bridge->funcs->hdmi_clear_avi_infoframe(bridge);
	case HDMI_INFOFRAME_TYPE_VENDOR:
		/* required */
}

static int drm_bridge_connector_write_avi_infoframe(struct drm_connector *connector,
						    const u8 *buffer, size_t len)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	return bridge->funcs->hdmi_write_avi_infoframe(bridge, buffer, len);
}

static int drm_bridge_connector_clear_hdmi_infoframe(struct drm_connector *connector)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	return bridge->funcs->hdmi_clear_hdmi_infoframe(bridge);
	case HDMI_INFOFRAME_TYPE_AUDIO:
}

static int drm_bridge_connector_write_hdmi_infoframe(struct drm_connector *connector,
						     const u8 *buffer, size_t len)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	return bridge->funcs->hdmi_write_hdmi_infoframe(bridge, buffer, len);
}

static int drm_bridge_connector_clear_audio_infoframe(struct drm_connector *connector)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO)
		return bridge->funcs->hdmi_clear_audio_infoframe(bridge);
		break;
	case HDMI_INFOFRAME_TYPE_DRM:
		if (bridge->ops & DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME)
			return bridge->funcs->hdmi_clear_hdr_drm_infoframe(bridge);
		break;
	case HDMI_INFOFRAME_TYPE_SPD:
		if (bridge->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME)
			return bridge->funcs->hdmi_clear_spd_infoframe(bridge);
		break;
	}

	drm_dbg_driver(connector->dev, "Unsupported HDMI InfoFrame %x\n", type);
	drm_dbg_driver(connector->dev, "Unsupported HDMI Audio InfoFrame\n");

	return 0;
}

static int drm_bridge_connector_write_infoframe(struct drm_connector *connector,
						enum hdmi_infoframe_type type,
static int drm_bridge_connector_write_audio_infoframe(struct drm_connector *connector,
						      const u8 *buffer, size_t len)
{
	struct drm_bridge_connector *bridge_connector =
@@ -450,28 +484,84 @@ static int drm_bridge_connector_write_infoframe(struct drm_connector *connector,
	if (!bridge)
		return -EINVAL;

	switch (type) {
	case HDMI_INFOFRAME_TYPE_AVI:
		/* required */
		return bridge->funcs->hdmi_write_avi_infoframe(bridge, buffer, len);
	case HDMI_INFOFRAME_TYPE_VENDOR:
		/* required */
		return bridge->funcs->hdmi_write_hdmi_infoframe(bridge, buffer, len);
	case HDMI_INFOFRAME_TYPE_AUDIO:
	if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO)
		return bridge->funcs->hdmi_write_audio_infoframe(bridge, buffer, len);
		break;
	case HDMI_INFOFRAME_TYPE_DRM:

	drm_dbg_driver(connector->dev, "Unsupported HDMI Audio InfoFrame\n");

	return 0;
}

static int drm_bridge_connector_clear_hdr_drm_infoframe(struct drm_connector *connector)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	if (bridge->ops & DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME)
		return bridge->funcs->hdmi_clear_hdr_drm_infoframe(bridge);

	drm_dbg_driver(connector->dev, "Unsupported HDMI HDR DRM InfoFrame\n");

	return 0;
}

static int drm_bridge_connector_write_hdr_drm_infoframe(struct drm_connector *connector,
							const u8 *buffer, size_t len)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	if (bridge->ops & DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME)
		return bridge->funcs->hdmi_write_hdr_drm_infoframe(bridge, buffer, len);
		break;
	case HDMI_INFOFRAME_TYPE_SPD:

	drm_dbg_driver(connector->dev, "Unsupported HDMI HDR DRM InfoFrame\n");

	return 0;
}

static int drm_bridge_connector_clear_spd_infoframe(struct drm_connector *connector)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	if (bridge->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME)
			return bridge->funcs->hdmi_write_spd_infoframe(bridge, buffer, len);
		break;
		return bridge->funcs->hdmi_clear_spd_infoframe(bridge);

	drm_dbg_driver(connector->dev, "Unsupported HDMI SPD InfoFrame\n");

	return 0;
}

	drm_dbg_driver(connector->dev, "Unsupported HDMI InfoFrame %x\n", type);
static int drm_bridge_connector_write_spd_infoframe(struct drm_connector *connector,
						    const u8 *buffer, size_t len)
{
	struct drm_bridge_connector *bridge_connector =
		to_drm_bridge_connector(connector);
	struct drm_bridge *bridge;

	bridge = bridge_connector->bridge_hdmi;
	if (!bridge)
		return -EINVAL;

	if (bridge->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME)
		return bridge->funcs->hdmi_write_spd_infoframe(bridge, buffer, len);

	drm_dbg_driver(connector->dev, "Unsupported HDMI SPD InfoFrame\n");

	return 0;
}
@@ -492,9 +582,27 @@ drm_bridge_connector_read_edid(struct drm_connector *connector)

static const struct drm_connector_hdmi_funcs drm_bridge_connector_hdmi_funcs = {
	.tmds_char_rate_valid = drm_bridge_connector_tmds_char_rate_valid,
	.clear_infoframe = drm_bridge_connector_clear_infoframe,
	.write_infoframe = drm_bridge_connector_write_infoframe,
	.read_edid = drm_bridge_connector_read_edid,
	.avi = {
		.clear_infoframe = drm_bridge_connector_clear_avi_infoframe,
		.write_infoframe = drm_bridge_connector_write_avi_infoframe,
	},
	.hdmi = {
		.clear_infoframe = drm_bridge_connector_clear_hdmi_infoframe,
		.write_infoframe = drm_bridge_connector_write_hdmi_infoframe,
	},
	.audio = {
		.clear_infoframe = drm_bridge_connector_clear_audio_infoframe,
		.write_infoframe = drm_bridge_connector_write_audio_infoframe,
	},
	.hdr_drm = {
		.clear_infoframe = drm_bridge_connector_clear_hdr_drm_infoframe,
		.write_infoframe = drm_bridge_connector_write_hdr_drm_infoframe,
	},
	.spd = {
		.clear_infoframe = drm_bridge_connector_clear_spd_infoframe,
		.write_infoframe = drm_bridge_connector_write_spd_infoframe,
	},
};

static int drm_bridge_connector_audio_startup(struct drm_connector *connector)
+44 −42
Original line number Diff line number Diff line
@@ -891,21 +891,21 @@ drm_hdmi_connector_mode_valid(struct drm_connector *connector,
}
EXPORT_SYMBOL(drm_hdmi_connector_mode_valid);

static int clear_device_infoframe(struct drm_connector *connector,
				  enum hdmi_infoframe_type type)
static int clear_infoframe(struct drm_connector *connector,
			   const struct drm_connector_infoframe_funcs *funcs,
			   const char *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);
	drm_dbg_kms(dev, "Clearing %s InfoFrame\n", type);

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

	ret = funcs->clear_infoframe(connector, type);
	ret = funcs->clear_infoframe(connector);
	if (ret) {
		drm_dbg_kms(dev, "Call failed: %d\n", ret);
		return ret;
@@ -914,39 +914,28 @@ static int clear_device_infoframe(struct drm_connector *connector,
	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)
static int write_infoframe(struct drm_connector *connector,
			   const struct drm_connector_infoframe_funcs *funcs,
			   const char *type,
			   struct drm_connector_hdmi_infoframe *new_frame)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_device *dev = connector->dev;
	u8 buffer[HDMI_INFOFRAME_SIZE(MAX)];
	int ret;
	int len;

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

	if (!funcs || !funcs->write_infoframe) {
	if (!funcs->write_infoframe) {
		drm_dbg_kms(dev, "Function not implemented, bailing.\n");
		return -EINVAL;
		return 0; /* XXX: temporal until we stop generating unsupported frames */
	}

	len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer));
	len = hdmi_infoframe_pack(&new_frame->data, buffer, sizeof(buffer));
	if (len < 0)
		return len;

	ret = funcs->write_infoframe(connector, frame->any.type, buffer, len);
	ret = funcs->write_infoframe(connector, buffer, len);
	if (ret) {
		drm_dbg_kms(dev, "Call failed: %d\n", ret);
		return ret;
@@ -955,27 +944,17 @@ static int write_device_infoframe(struct drm_connector *connector,
	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,
				    const struct drm_connector_infoframe_funcs *funcs,
				    const char *type,
				    struct drm_connector_hdmi_infoframe *old_frame,
				    struct drm_connector_hdmi_infoframe *new_frame)
{
	if (new_frame->set)
		return write_infoframe(connector, new_frame);
		return write_infoframe(connector, funcs, type, new_frame);

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

	return 0;
}
@@ -995,6 +974,7 @@ static int write_or_clear_infoframe(struct drm_connector *connector,
int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector,
						       struct drm_atomic_state *state)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_connector_state *old_conn_state =
		drm_atomic_get_old_connector_state(state, connector);
	struct drm_connector_state *new_conn_state =
@@ -1005,9 +985,15 @@ int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *con
	if (!info->is_hdmi)
		return 0;

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

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

	ret = write_or_clear_infoframe(connector,
				       &funcs->avi, "AVI",
				       &old_conn_state->hdmi.infoframes.avi,
				       &new_conn_state->hdmi.infoframes.avi);
	if (ret)
@@ -1015,18 +1001,21 @@ int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *con

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

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

	ret = write_or_clear_infoframe(connector,
				       &funcs->spd, "SPD",
				       &old_conn_state->hdmi.infoframes.spd,
				       &new_conn_state->hdmi.infoframes.spd);
	if (ret)
@@ -1034,6 +1023,7 @@ int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *con

	if (info->has_hdmi_infoframe) {
		ret = write_or_clear_infoframe(connector,
					       &funcs->hdmi, "HDMI-VS",
					       &old_conn_state->hdmi.infoframes.hdmi,
					       &new_conn_state->hdmi.infoframes.hdmi);
		if (ret)
@@ -1062,6 +1052,7 @@ int
drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector,
							struct hdmi_audio_infoframe *frame)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_connector_hdmi_infoframe *infoframe =
		&connector->hdmi.infoframes.audio;
	struct drm_display_info *info = &connector->display_info;
@@ -1070,12 +1061,17 @@ drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *co
	if (!info->is_hdmi)
		return 0;

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

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

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

	ret = write_infoframe(connector, infoframe);
	ret = write_infoframe(connector, &funcs->audio, "Audio", infoframe);

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

@@ -1097,6 +1093,7 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_audio_infoframe);
int
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *connector)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_connector_hdmi_infoframe *infoframe =
		&connector->hdmi.infoframes.audio;
	struct drm_display_info *info = &connector->display_info;
@@ -1105,11 +1102,16 @@ drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *con
	if (!info->is_hdmi)
		return 0;

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

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

	infoframe->set = false;

	ret = clear_infoframe(connector, infoframe);
	ret = clear_infoframe(connector, &funcs->audio, "Audio");

	memset(&infoframe->data, 0, sizeof(infoframe->data));

+4 −2
Original line number Diff line number Diff line
@@ -600,8 +600,10 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
	if (!(max_bpc == 8 || max_bpc == 10 || max_bpc == 12))
		return -EINVAL;

	if (!hdmi_funcs->clear_infoframe ||
	    !hdmi_funcs->write_infoframe)
	if (!hdmi_funcs->avi.clear_infoframe ||
	    !hdmi_funcs->avi.write_infoframe ||
	    !hdmi_funcs->hdmi.clear_infoframe ||
	    !hdmi_funcs->hdmi.write_infoframe)
		return -EINVAL;

	ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc);
+26 −13
Original line number Diff line number Diff line
@@ -40,32 +40,39 @@
#define drm_connector_to_sun4i_hdmi(c)		\
	container_of_const(c, struct sun4i_hdmi, connector)

static int sun4i_hdmi_clear_infoframe(struct drm_connector *connector,
				      enum hdmi_infoframe_type type)
static int sun4i_hdmi_clear_avi_infoframe(struct drm_connector *connector)
{
	drm_warn_once(connector->dev, "clearing of AVI infoframe is not implemented\n");

	return 0;
}

static int sun4i_hdmi_write_infoframe(struct drm_connector *connector,
				      enum hdmi_infoframe_type type,
static int sun4i_hdmi_write_avi_infoframe(struct drm_connector *connector,
					  const u8 *buffer, size_t len)
{
	struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
	int i;

	if (type != HDMI_INFOFRAME_TYPE_AVI) {
		drm_err(connector->dev,
			"Unsupported infoframe type: %u\n", type);
	for (i = 0; i < len; i++)
		writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));

	return 0;

}

	for (i = 0; i < len; i++)
		writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
static int sun4i_hdmi_clear_hdmi_infoframe(struct drm_connector *connector)
{
	drm_warn_once(connector->dev, "HDMI VSI not implemented\n");

	return 0;
}

static int sun4i_hdmi_write_hdmi_infoframe(struct drm_connector *connector,
					   const u8 *buffer, size_t len)
{
	drm_warn_once(connector->dev, "HDMI VSI not implemented\n");

	return 0;
}

static void sun4i_hdmi_disable(struct drm_encoder *encoder,
@@ -244,8 +251,14 @@ static struct i2c_adapter *sun4i_hdmi_get_ddc(struct device *dev)

static const struct drm_connector_hdmi_funcs sun4i_hdmi_hdmi_connector_funcs = {
	.tmds_char_rate_valid	= sun4i_hdmi_connector_clock_valid,
	.clear_infoframe	= sun4i_hdmi_clear_infoframe,
	.write_infoframe	= sun4i_hdmi_write_infoframe,
	.avi = {
		.clear_infoframe	= sun4i_hdmi_clear_avi_infoframe,
		.write_infoframe	= sun4i_hdmi_write_avi_infoframe,
	},
	.hdmi = {
		.clear_infoframe	= sun4i_hdmi_clear_hdmi_infoframe,
		.write_infoframe	= sun4i_hdmi_write_hdmi_infoframe,
	},
};

static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
+9 −5
Original line number Diff line number Diff line
@@ -25,22 +25,26 @@ struct drm_connector_init_priv {
	struct i2c_adapter ddc;
};

static int accept_infoframe_clear_infoframe(struct drm_connector *connector,
					    enum hdmi_infoframe_type type)
static int accept_infoframe_clear_infoframe(struct drm_connector *connector)
{
	return 0;
}

static int accept_infoframe_write_infoframe(struct drm_connector *connector,
					    enum hdmi_infoframe_type type,
					    const u8 *buffer, size_t len)
{
	return 0;
}

static const struct drm_connector_hdmi_funcs dummy_hdmi_funcs = {
	.avi = {
		.clear_infoframe = accept_infoframe_clear_infoframe,
		.write_infoframe = accept_infoframe_write_infoframe,
	},
	.hdmi = {
		.clear_infoframe = accept_infoframe_clear_infoframe,
		.write_infoframe = accept_infoframe_write_infoframe,
	},
};

static const struct drm_connector_funcs dummy_funcs = {
Loading