From 22affbca0818cce5ddaa6c8d67186867fe87a32e Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:39 +0100 Subject: [PATCH 1/2] BACKPORT: drm/bridge: add next_bridge pointer to struct drm_bridge Many bridge drivers store a next_bridge pointer in their private data and use it for attach and sometimes other purposes. This is going to be risky when bridge hot-unplug is used. Considering this example scenario: 1. pipeline: encoder --> bridge A --> bridge B --> bridge C 2. encoder takes a reference to bridge B 3. bridge B takes a next_bridge reference to bridge C 4. encoder calls (bridge B)->b_foo(), which in turns references next_bridge, e.g.: b_foo() { bar(b->next_bridge); } If bridges B and C are removed, bridge C can be freed but B is still allocated because the encoder holds a reference to B. So when step 4 happens, 'b->next-bridge' would be a use-after-free. Calling drm_bridge_put() in the B bridge .remove function does not solve the problem as it leaves a (potentially long) risk window between B removal and the final deallocation of B. A safe moment to put the B reference is in __drm_bridge_free(), when the last reference has been put. This can be done by drivers in the .destroy func. However to avoid the need for so many drivers to implement a .destroy func, just offer a next_bridge pointer to all bridges that is automatically put it in __drm_bridge_free(), exactly when the .destroy func is called. Suggested-by: Maxime Ripard Link: https://lore.kernel.org/all/20251201-thick-jasmine-oarfish-1eceb0@houat/ Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-6-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli Signed-off-by: Vishnu Saini --- drivers/gpu/drm/drm_bridge.c | 3 +++ include/drm/drm_bridge.h | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index d031447eebc95..8282434a68e79 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -206,6 +206,9 @@ static void __drm_bridge_free(struct kref *kref) if (bridge->funcs->destroy) bridge->funcs->destroy(bridge); + + drm_bridge_put(bridge->next_bridge); + kfree(bridge->container); } diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 57d9a3c062124..8bfea1e0a62d9 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1153,6 +1153,17 @@ struct drm_bridge { * @hpd_cb. */ void *hpd_data; + + /** + * @next_bridge: Pointer to the following bridge, automatically put + * when this bridge is freed (i.e. at destroy time). This is for + * drivers needing to store a pointer to the next bridge in the + * chain, and ensures any code still holding a reference to this + * bridge after its removal cannot use-after-free the next + * bridge. Any other bridge pointers stored by the driver must be + * put in the .destroy callback by driver code. + */ + struct drm_bridge *next_bridge; }; static inline struct drm_bridge * From 4ed41de81c1e6d90fc743370c7db0d611a56134f Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 7 Jan 2026 20:15:02 +0200 Subject: [PATCH 2/2] BACKPORT: drm/bridge: refactor HDMI InfoFrame callbacks Having only a single set of callbacks, hdmi_clear_infoframe and hdmi_write_infoframe, bridge drivers don't have an easy way to signal to the DRM framework, which InfoFrames are actually supported by the hardware and by the driver and which are not. Also, it makes it extremely easy for HDMI bridge drivers to skip implementing the seemingly required InfoFrames (e.g. HDMI VSI). Last, but not least, those callbacks take a single 'type' parameter, which makes it impossible to implement support for multiple VSIs (which will be required once we start working on HDMI Forum VSI). Split the callbacks into a per-InfoFrame-kind pairs, letting the bridge drivers actually signal supported features. The implementation follows the overall drm_bridge design, where the bridge has a single drm_bridge_funcs implementation and signals, which functions are to be called using the drm_bridge->ops flags. The AVI and HDMI VSI are assumed to be required for a normal HDMI operation (with the drivers getting a drm_warn_once() stub implementation if one is missing). The Audio InfoFrame is handled by the existing DRM_BRIDGE_OP_HDMI_AUDIO, while the SPD and HDR DRM InfoFrames got new drm_bridge_ops values. Acked-by: Maxime Ripard Link: https://patch.msgid.link/20260107-limit-infoframes-2-v4-5-213d0d3bd490@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov Signed-off-by: Vishnu Saini --- drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 180 +++++++++------- drivers/gpu/drm/bridge/ite-it6263.c | 95 ++++----- drivers/gpu/drm/bridge/lontium-lt9611.c | 143 +++++++------ drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 110 ++++++---- .../gpu/drm/display/drm_bridge_connector.c | 70 ++++++- drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 195 +++++++++--------- drivers/gpu/drm/rockchip/rk3066_hdmi.c | 47 +++-- include/drm/drm_bridge.h | 127 ++++++++++-- 8 files changed, 618 insertions(+), 349 deletions(-) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index b9be865413075..1050bb62280bb 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -887,88 +887,111 @@ static const struct drm_edid *adv7511_bridge_edid_read(struct drm_bridge *bridge return adv7511_edid_read(adv, connector); } -static int adv7511_bridge_hdmi_clear_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type) +static int adv7511_bridge_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) { struct adv7511 *adv7511 = bridge_to_adv7511(bridge); - switch (type) { - case HDMI_INFOFRAME_TYPE_AUDIO: - adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); - break; - case HDMI_INFOFRAME_TYPE_AVI: - adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); - break; - case HDMI_INFOFRAME_TYPE_SPD: - adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPD); - break; - case HDMI_INFOFRAME_TYPE_VENDOR: - adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); - break; - default: - drm_dbg_driver(adv7511->bridge.dev, "Unsupported HDMI InfoFrame %x\n", type); - break; - } + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); return 0; } -static int adv7511_bridge_hdmi_write_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) +static int adv7511_bridge_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) { struct adv7511 *adv7511 = bridge_to_adv7511(bridge); - switch (type) { - case HDMI_INFOFRAME_TYPE_AUDIO: - /* send current Audio infoframe values while updating */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, - BIT(5), BIT(5)); - - /* The Audio infoframe id is not configurable */ - regmap_bulk_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME_VERSION, - buffer + 1, len - 1); - - /* use Audio infoframe updated info */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, - BIT(5), 0); - - adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); - break; - case HDMI_INFOFRAME_TYPE_AVI: - /* send current AVI infoframe values while updating */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, - BIT(6), BIT(6)); - - /* The AVI infoframe id is not configurable */ - regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION, - buffer + 1, len - 1); - - regmap_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME_LENGTH, 0x2); - regmap_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME(1), 0x1); - - /* use AVI infoframe updated info */ - regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, - BIT(6), 0); - - adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); - break; - case HDMI_INFOFRAME_TYPE_SPD: - adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPD); - regmap_bulk_write(adv7511->regmap_packet, ADV7511_PACKET_SPD(0), - buffer, len); - adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_SPD); - break; - case HDMI_INFOFRAME_TYPE_VENDOR: - adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); - regmap_bulk_write(adv7511->regmap_packet, ADV7511_PACKET_SPARE1(0), - buffer, len); - adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); - break; - default: - drm_dbg_driver(adv7511->bridge.dev, "Unsupported HDMI InfoFrame %x\n", type); - break; - } + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); + + return 0; +} + +static int adv7511_bridge_hdmi_clear_spd_infoframe(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPD); + + return 0; +} + +static int adv7511_bridge_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); + + return 0; +} + +static int adv7511_bridge_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + /* send current Audio infoframe values while updating */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(5), BIT(5)); + + /* The Audio infoframe id is not configurable */ + regmap_bulk_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME_VERSION, + buffer + 1, len - 1); + + /* use Audio infoframe updated info */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(5), 0); + + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); + + return 0; +} + +static int adv7511_bridge_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + /* send current AVI infoframe values while updating */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(6), BIT(6)); + + /* The AVI infoframe id is not configurable */ + regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION, + buffer + 1, len - 1); + + regmap_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME_LENGTH, 0x2); + regmap_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME(1), 0x1); + + /* use AVI infoframe updated info */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(6), 0); + + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); + + return 0; +} + +static int adv7511_bridge_hdmi_write_spd_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPD); + regmap_bulk_write(adv7511->regmap_packet, ADV7511_PACKET_SPD(0), + buffer, len); + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_SPD); + + return 0; +} + +static int adv7511_bridge_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); + regmap_bulk_write(adv7511->regmap_packet, ADV7511_PACKET_SPARE1(0), + buffer, len); + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); return 0; } @@ -986,8 +1009,14 @@ static const struct drm_bridge_funcs adv7511_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, .hdmi_tmds_char_rate_valid = adv7511_bridge_hdmi_tmds_char_rate_valid, - .hdmi_clear_infoframe = adv7511_bridge_hdmi_clear_infoframe, - .hdmi_write_infoframe = adv7511_bridge_hdmi_write_infoframe, + .hdmi_clear_audio_infoframe = adv7511_bridge_hdmi_clear_audio_infoframe, + .hdmi_write_audio_infoframe = adv7511_bridge_hdmi_write_audio_infoframe, + .hdmi_clear_avi_infoframe = adv7511_bridge_hdmi_clear_avi_infoframe, + .hdmi_write_avi_infoframe = adv7511_bridge_hdmi_write_avi_infoframe, + .hdmi_clear_spd_infoframe = adv7511_bridge_hdmi_clear_spd_infoframe, + .hdmi_write_spd_infoframe = adv7511_bridge_hdmi_write_spd_infoframe, + .hdmi_clear_hdmi_infoframe = adv7511_bridge_hdmi_clear_hdmi_infoframe, + .hdmi_write_hdmi_infoframe = adv7511_bridge_hdmi_write_hdmi_infoframe, .hdmi_audio_startup = adv7511_hdmi_audio_startup, .hdmi_audio_prepare = adv7511_hdmi_audio_prepare, @@ -1322,7 +1351,8 @@ static int adv7511_probe(struct i2c_client *i2c) adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | - DRM_BRIDGE_OP_HDMI; + DRM_BRIDGE_OP_HDMI | + DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME; if (adv7511->i2c_main->irq) adv7511->bridge.ops |= DRM_BRIDGE_OP_HPD; diff --git a/drivers/gpu/drm/bridge/ite-it6263.c b/drivers/gpu/drm/bridge/ite-it6263.c index 2eb8fba7016cb..3991fb76143c9 100644 --- a/drivers/gpu/drm/bridge/ite-it6263.c +++ b/drivers/gpu/drm/bridge/ite-it6263.c @@ -759,61 +759,62 @@ it6263_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, return MODE_OK; } -static int it6263_hdmi_clear_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type) +static int it6263_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) { struct it6263 *it = bridge_to_it6263(bridge); - switch (type) { - case HDMI_INFOFRAME_TYPE_AVI: - regmap_write(it->hdmi_regmap, HDMI_REG_AVI_INFOFRM_CTRL, 0); - break; - case HDMI_INFOFRAME_TYPE_VENDOR: - regmap_write(it->hdmi_regmap, HDMI_REG_PKT_NULL_CTRL, 0); - break; - default: - dev_dbg(it->dev, "unsupported HDMI infoframe 0x%x\n", type); - } + regmap_write(it->hdmi_regmap, HDMI_REG_AVI_INFOFRM_CTRL, 0); + + return 0; +} + +static int it6263_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + struct it6263 *it = bridge_to_it6263(bridge); + + regmap_write(it->hdmi_regmap, HDMI_REG_PKT_NULL_CTRL, 0); return 0; } -static int it6263_hdmi_write_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) +static int it6263_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) { struct it6263 *it = bridge_to_it6263(bridge); struct regmap *regmap = it->hdmi_regmap; - switch (type) { - case HDMI_INFOFRAME_TYPE_AVI: - /* write the first AVI infoframe data byte chunk(DB1-DB5) */ - regmap_bulk_write(regmap, HDMI_REG_AVI_DB1, - &buffer[HDMI_INFOFRAME_HEADER_SIZE], - HDMI_AVI_DB_CHUNK1_SIZE); - - /* write the second AVI infoframe data byte chunk(DB6-DB13) */ - regmap_bulk_write(regmap, HDMI_REG_AVI_DB6, - &buffer[HDMI_INFOFRAME_HEADER_SIZE + - HDMI_AVI_DB_CHUNK1_SIZE], - HDMI_AVI_DB_CHUNK2_SIZE); - - /* write checksum */ - regmap_write(regmap, HDMI_REG_AVI_CSUM, buffer[3]); - - regmap_write(regmap, HDMI_REG_AVI_INFOFRM_CTRL, - ENABLE_PKT | REPEAT_PKT); - break; - case HDMI_INFOFRAME_TYPE_VENDOR: - /* write header and payload */ - regmap_bulk_write(regmap, HDMI_REG_PKT_HB(0), buffer, len); - - regmap_write(regmap, HDMI_REG_PKT_NULL_CTRL, - ENABLE_PKT | REPEAT_PKT); - break; - default: - dev_dbg(it->dev, "unsupported HDMI infoframe 0x%x\n", type); - } + /* write the first AVI infoframe data byte chunk(DB1-DB5) */ + regmap_bulk_write(regmap, HDMI_REG_AVI_DB1, + &buffer[HDMI_INFOFRAME_HEADER_SIZE], + HDMI_AVI_DB_CHUNK1_SIZE); + + /* write the second AVI infoframe data byte chunk(DB6-DB13) */ + regmap_bulk_write(regmap, HDMI_REG_AVI_DB6, + &buffer[HDMI_INFOFRAME_HEADER_SIZE + + HDMI_AVI_DB_CHUNK1_SIZE], + HDMI_AVI_DB_CHUNK2_SIZE); + + /* write checksum */ + regmap_write(regmap, HDMI_REG_AVI_CSUM, buffer[3]); + + regmap_write(regmap, HDMI_REG_AVI_INFOFRM_CTRL, + ENABLE_PKT | REPEAT_PKT); + + return 0; +} + +static int it6263_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct it6263 *it = bridge_to_it6263(bridge); + struct regmap *regmap = it->hdmi_regmap; + + /* write header and payload */ + regmap_bulk_write(regmap, HDMI_REG_PKT_HB(0), buffer, len); + + regmap_write(regmap, HDMI_REG_PKT_NULL_CTRL, + ENABLE_PKT | REPEAT_PKT); + return 0; } @@ -830,8 +831,10 @@ static const struct drm_bridge_funcs it6263_bridge_funcs = { .edid_read = it6263_bridge_edid_read, .atomic_get_input_bus_fmts = it6263_bridge_atomic_get_input_bus_fmts, .hdmi_tmds_char_rate_valid = it6263_hdmi_tmds_char_rate_valid, - .hdmi_clear_infoframe = it6263_hdmi_clear_infoframe, - .hdmi_write_infoframe = it6263_hdmi_write_infoframe, + .hdmi_clear_avi_infoframe = it6263_hdmi_clear_avi_infoframe, + .hdmi_write_avi_infoframe = it6263_hdmi_write_avi_infoframe, + .hdmi_clear_hdmi_infoframe = it6263_hdmi_clear_hdmi_infoframe, + .hdmi_write_hdmi_infoframe = it6263_hdmi_write_hdmi_infoframe, }; static int it6263_probe(struct i2c_client *client) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c index a2d032ee47447..0628d8e737abb 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -843,84 +843,96 @@ lt9611_atomic_get_input_bus_fmts(struct drm_bridge *bridge, #define LT9611_INFOFRAME_AUDIO 0x02 #define LT9611_INFOFRAME_AVI 0x08 #define LT9611_INFOFRAME_SPD 0x10 -#define LT9611_INFOFRAME_VENDOR 0x20 +#define LT9611_INFOFRAME_HDMI 0x20 -static int lt9611_hdmi_clear_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type) +static int lt9611_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) { struct lt9611 *lt9611 = bridge_to_lt9611(bridge); - unsigned int mask; - switch (type) { - case HDMI_INFOFRAME_TYPE_AUDIO: - mask = LT9611_INFOFRAME_AUDIO; - break; + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_AUDIO, 0); - case HDMI_INFOFRAME_TYPE_AVI: - mask = LT9611_INFOFRAME_AVI; - break; + return 0; +} - case HDMI_INFOFRAME_TYPE_SPD: - mask = LT9611_INFOFRAME_SPD; - break; +static int lt9611_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); - case HDMI_INFOFRAME_TYPE_VENDOR: - mask = LT9611_INFOFRAME_VENDOR; - break; + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_AVI, 0); - default: - drm_dbg_driver(lt9611->bridge.dev, "Unsupported HDMI InfoFrame %x\n", type); - mask = 0; - break; - } + return 0; +} + +static int lt9611_hdmi_clear_spd_infoframe(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); - if (mask) - regmap_update_bits(lt9611->regmap, 0x843d, mask, 0); + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_SPD, 0); return 0; } -static int lt9611_hdmi_write_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) +static int lt9611_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_HDMI, 0); + + return 0; +} + +static int lt9611_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) { struct lt9611 *lt9611 = bridge_to_lt9611(bridge); - unsigned int mask, addr; int i; - switch (type) { - case HDMI_INFOFRAME_TYPE_AUDIO: - mask = LT9611_INFOFRAME_AUDIO; - addr = 0x84b2; - break; - - case HDMI_INFOFRAME_TYPE_AVI: - mask = LT9611_INFOFRAME_AVI; - addr = 0x8440; - break; - - case HDMI_INFOFRAME_TYPE_SPD: - mask = LT9611_INFOFRAME_SPD; - addr = 0x8493; - break; - - case HDMI_INFOFRAME_TYPE_VENDOR: - mask = LT9611_INFOFRAME_VENDOR; - addr = 0x8474; - break; - - default: - drm_dbg_driver(lt9611->bridge.dev, "Unsupported HDMI InfoFrame %x\n", type); - mask = 0; - break; - } + for (i = 0; i < len; i++) + regmap_write(lt9611->regmap, 0x84b2 + i, buffer[i]); - if (mask) { - for (i = 0; i < len; i++) - regmap_write(lt9611->regmap, addr + i, buffer[i]); + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_AUDIO, LT9611_INFOFRAME_AUDIO); - regmap_update_bits(lt9611->regmap, 0x843d, mask, mask); - } + return 0; +} + +static int lt9611_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + int i; + + for (i = 0; i < len; i++) + regmap_write(lt9611->regmap, 0x8440 + i, buffer[i]); + + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_AVI, LT9611_INFOFRAME_AVI); + + return 0; +} + +static int lt9611_hdmi_write_spd_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + int i; + + for (i = 0; i < len; i++) + regmap_write(lt9611->regmap, 0x8493 + i, buffer[i]); + + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_SPD, LT9611_INFOFRAME_SPD); + + return 0; +} + +static int lt9611_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + int i; + + for (i = 0; i < len; i++) + regmap_write(lt9611->regmap, 0x8474 + i, buffer[i]); + + regmap_update_bits(lt9611->regmap, 0x843d, LT9611_INFOFRAME_HDMI, LT9611_INFOFRAME_HDMI); return 0; } @@ -1003,8 +1015,14 @@ static const struct drm_bridge_funcs lt9611_bridge_funcs = { .atomic_get_input_bus_fmts = lt9611_atomic_get_input_bus_fmts, .hdmi_tmds_char_rate_valid = lt9611_hdmi_tmds_char_rate_valid, - .hdmi_write_infoframe = lt9611_hdmi_write_infoframe, - .hdmi_clear_infoframe = lt9611_hdmi_clear_infoframe, + .hdmi_write_audio_infoframe = lt9611_hdmi_write_audio_infoframe, + .hdmi_clear_audio_infoframe = lt9611_hdmi_clear_audio_infoframe, + .hdmi_write_avi_infoframe = lt9611_hdmi_write_avi_infoframe, + .hdmi_clear_avi_infoframe = lt9611_hdmi_clear_avi_infoframe, + .hdmi_write_spd_infoframe = lt9611_hdmi_write_spd_infoframe, + .hdmi_clear_spd_infoframe = lt9611_hdmi_clear_spd_infoframe, + .hdmi_write_hdmi_infoframe = lt9611_hdmi_write_hdmi_infoframe, + .hdmi_clear_hdmi_infoframe = lt9611_hdmi_clear_hdmi_infoframe, .hdmi_audio_startup = lt9611_hdmi_audio_startup, .hdmi_audio_prepare = lt9611_hdmi_audio_prepare, @@ -1132,7 +1150,8 @@ static int lt9611_probe(struct i2c_client *client) lt9611->bridge.of_node = client->dev.of_node; lt9611->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_MODES | - DRM_BRIDGE_OP_HDMI | DRM_BRIDGE_OP_HDMI_AUDIO; + DRM_BRIDGE_OP_HDMI | DRM_BRIDGE_OP_HDMI_AUDIO | + DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME; lt9611->bridge.type = DRM_MODE_CONNECTOR_HDMIA; lt9611->bridge.vendor = "Lontium"; lt9611->bridge.product = "LT9611"; diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index d302455875167..7331b7a0457e8 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -913,57 +914,85 @@ dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge, return MODE_OK; } -static int dw_hdmi_qp_bridge_clear_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type) +static int dw_hdmi_qp_bridge_clear_avi_infoframe(struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - switch (type) { - case HDMI_INFOFRAME_TYPE_AVI: - dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, - PKTSCHED_PKT_EN); - break; + dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, + PKTSCHED_PKT_EN); - case HDMI_INFOFRAME_TYPE_DRM: - dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); - break; + return 0; +} - case HDMI_INFOFRAME_TYPE_AUDIO: - dw_hdmi_qp_mod(hdmi, 0, - PKTSCHED_ACR_TX_EN | - PKTSCHED_AUDS_TX_EN | - PKTSCHED_AUDI_TX_EN, - PKTSCHED_PKT_EN); - break; - default: - dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type); - } +static int dw_hdmi_qp_bridge_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + /* FIXME: add support for this InfoFrame */ + + drm_warn_once(bridge->encoder->dev, "HDMI VSI not supported\n"); return 0; } -static int dw_hdmi_qp_bridge_write_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) +static int dw_hdmi_qp_bridge_clear_hdr_drm_infoframe(struct drm_bridge *bridge) { struct dw_hdmi_qp *hdmi = bridge->driver_private; - dw_hdmi_qp_bridge_clear_infoframe(bridge, type); + dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); - switch (type) { - case HDMI_INFOFRAME_TYPE_AVI: - return dw_hdmi_qp_config_avi_infoframe(hdmi, buffer, len); + return 0; +} - case HDMI_INFOFRAME_TYPE_DRM: - return dw_hdmi_qp_config_drm_infoframe(hdmi, buffer, len); +static int dw_hdmi_qp_bridge_clear_audio_infoframe(struct drm_bridge *bridge) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; - case HDMI_INFOFRAME_TYPE_AUDIO: - return dw_hdmi_qp_config_audio_infoframe(hdmi, buffer, len); + dw_hdmi_qp_mod(hdmi, 0, + PKTSCHED_ACR_TX_EN | + PKTSCHED_AUDS_TX_EN | + PKTSCHED_AUDI_TX_EN, + PKTSCHED_PKT_EN); - default: - dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type); - return 0; - } + return 0; +} + +static int dw_hdmi_qp_bridge_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + + dw_hdmi_qp_bridge_clear_avi_infoframe(bridge); + + return dw_hdmi_qp_config_avi_infoframe(hdmi, buffer, len); +} + +static int dw_hdmi_qp_bridge_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + dw_hdmi_qp_bridge_clear_hdmi_infoframe(bridge); + + /* FIXME: add support for the HDMI VSI */ + + return 0; +} + +static int dw_hdmi_qp_bridge_write_hdr_drm_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + + dw_hdmi_qp_bridge_clear_hdr_drm_infoframe(bridge); + + return dw_hdmi_qp_config_drm_infoframe(hdmi, buffer, len); +} + +static int dw_hdmi_qp_bridge_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct dw_hdmi_qp *hdmi = bridge->driver_private; + + dw_hdmi_qp_bridge_clear_audio_infoframe(bridge); + + return dw_hdmi_qp_config_audio_infoframe(hdmi, buffer, len); } static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { @@ -975,8 +1004,14 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .detect = dw_hdmi_qp_bridge_detect, .edid_read = dw_hdmi_qp_bridge_edid_read, .hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid, - .hdmi_clear_infoframe = dw_hdmi_qp_bridge_clear_infoframe, - .hdmi_write_infoframe = dw_hdmi_qp_bridge_write_infoframe, + .hdmi_clear_avi_infoframe = dw_hdmi_qp_bridge_clear_avi_infoframe, + .hdmi_write_avi_infoframe = dw_hdmi_qp_bridge_write_avi_infoframe, + .hdmi_clear_hdmi_infoframe = dw_hdmi_qp_bridge_clear_hdmi_infoframe, + .hdmi_write_hdmi_infoframe = dw_hdmi_qp_bridge_write_hdmi_infoframe, + .hdmi_clear_hdr_drm_infoframe = dw_hdmi_qp_bridge_clear_hdr_drm_infoframe, + .hdmi_write_hdr_drm_infoframe = dw_hdmi_qp_bridge_write_hdr_drm_infoframe, + .hdmi_clear_audio_infoframe = dw_hdmi_qp_bridge_clear_audio_infoframe, + .hdmi_write_audio_infoframe = dw_hdmi_qp_bridge_write_audio_infoframe, .hdmi_audio_startup = dw_hdmi_qp_audio_enable, .hdmi_audio_shutdown = dw_hdmi_qp_audio_disable, .hdmi_audio_prepare = dw_hdmi_qp_audio_prepare, @@ -1081,6 +1116,7 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HDMI | DRM_BRIDGE_OP_HDMI_AUDIO | + DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME | DRM_BRIDGE_OP_HPD; hdmi->bridge.of_node = pdev->dev.of_node; hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index baacd21e7341f..ae02d5ae11ffe 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -413,7 +413,30 @@ static int drm_bridge_connector_clear_infoframe(struct drm_connector *connector, if (!bridge) return -EINVAL; - return bridge->funcs->hdmi_clear_infoframe(bridge, type); + switch (type) { + case HDMI_INFOFRAME_TYPE_AVI: + /* required */ + return bridge->funcs->hdmi_clear_avi_infoframe(bridge); + case HDMI_INFOFRAME_TYPE_VENDOR: + /* required */ + return bridge->funcs->hdmi_clear_hdmi_infoframe(bridge); + case HDMI_INFOFRAME_TYPE_AUDIO: + 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); + + return 0; } static int drm_bridge_connector_write_infoframe(struct drm_connector *connector, @@ -428,7 +451,30 @@ static int drm_bridge_connector_write_infoframe(struct drm_connector *connector, if (!bridge) return -EINVAL; - return bridge->funcs->hdmi_write_infoframe(bridge, type, buffer, len); + 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: + 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: + if (bridge->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME) + return bridge->funcs->hdmi_write_spd_infoframe(bridge, buffer, len); + break; + } + + drm_dbg_driver(connector->dev, "Unsupported HDMI InfoFrame %x\n", type); + + return 0; } static const struct drm_edid * @@ -684,8 +730,20 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, if (bridge->ops & DRM_BRIDGE_OP_HDMI) { if (bridge_connector->bridge_hdmi) return ERR_PTR(-EBUSY); - if (!bridge->funcs->hdmi_write_infoframe || - !bridge->funcs->hdmi_clear_infoframe) + if (!bridge->funcs->hdmi_write_avi_infoframe || + !bridge->funcs->hdmi_clear_avi_infoframe || + !bridge->funcs->hdmi_write_hdmi_infoframe || + !bridge->funcs->hdmi_clear_hdmi_infoframe) + return ERR_PTR(-EINVAL); + + if (bridge->ops & DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME && + (!bridge->funcs->hdmi_write_hdr_drm_infoframe || + !bridge->funcs->hdmi_clear_hdr_drm_infoframe)) + return ERR_PTR(-EINVAL); + + if (bridge->ops & DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME && + (!bridge->funcs->hdmi_write_spd_infoframe || + !bridge->funcs->hdmi_clear_spd_infoframe)) return ERR_PTR(-EINVAL); bridge_connector->bridge_hdmi = bridge; @@ -707,7 +765,9 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, !bridge->hdmi_audio_spdif_playback) return ERR_PTR(-EINVAL); - if (!bridge->funcs->hdmi_audio_prepare || + if (!bridge->funcs->hdmi_write_audio_infoframe || + !bridge->funcs->hdmi_clear_audio_infoframe || + !bridge->funcs->hdmi_audio_prepare || !bridge->funcs->hdmi_audio_shutdown) return ERR_PTR(-EINVAL); diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c index 46fd58646d32f..98cd490e7ab0f 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c @@ -54,9 +54,80 @@ static void power_off(struct drm_bridge *bridge) #define SPD_IFRAME_LINE_NUMBER 1 #define VENSPEC_IFRAME_LINE_NUMBER 3 -static int msm_hdmi_config_avi_infoframe(struct hdmi *hdmi, - const u8 *buffer, size_t len) +static int msm_hdmi_bridge_clear_avi_infoframe(struct drm_bridge *bridge) { + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; + u32 val; + + val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); + val &= ~(HDMI_INFOFRAME_CTRL0_AVI_SEND | + HDMI_INFOFRAME_CTRL0_AVI_CONT); + hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + + val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); + val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK; + hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + + return 0; +} + +static int msm_hdmi_bridge_clear_audio_infoframe(struct drm_bridge *bridge) +{ + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; + u32 val; + + val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); + val &= ~(HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE); + hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + + val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); + val &= ~HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK; + hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + + return 0; +} + +static int msm_hdmi_bridge_clear_spd_infoframe(struct drm_bridge *bridge) +{ + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; + u32 val; + + val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); + val &= ~(HDMI_GEN_PKT_CTRL_GENERIC1_SEND | + HDMI_GEN_PKT_CTRL_GENERIC1_CONT | + HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK); + hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + + return 0; +} + +static int msm_hdmi_bridge_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; + u32 val; + + val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); + val &= ~(HDMI_GEN_PKT_CTRL_GENERIC0_SEND | + HDMI_GEN_PKT_CTRL_GENERIC0_CONT | + HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | + HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK); + hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + + return 0; +} + +static int msm_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; u32 buf[4] = {}; u32 val; int i; @@ -67,6 +138,8 @@ static int msm_hdmi_config_avi_infoframe(struct hdmi *hdmi, return -EINVAL; } + msm_hdmi_bridge_clear_avi_infoframe(bridge); + /* * the AVI_INFOx registers don't map exactly to how the AVI infoframes * are packed according to the spec. The checksum from the header is @@ -93,9 +166,11 @@ static int msm_hdmi_config_avi_infoframe(struct hdmi *hdmi, return 0; } -static int msm_hdmi_config_audio_infoframe(struct hdmi *hdmi, - const u8 *buffer, size_t len) +static int msm_hdmi_bridge_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) { + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; u32 val; if (len != HDMI_INFOFRAME_SIZE(AUDIO)) { @@ -104,6 +179,8 @@ static int msm_hdmi_config_audio_infoframe(struct hdmi *hdmi, return -EINVAL; } + msm_hdmi_bridge_clear_audio_infoframe(bridge); + hdmi_write(hdmi, REG_HDMI_AUDIO_INFO0, buffer[3] | buffer[4] << 8 | @@ -126,9 +203,11 @@ static int msm_hdmi_config_audio_infoframe(struct hdmi *hdmi, return 0; } -static int msm_hdmi_config_spd_infoframe(struct hdmi *hdmi, - const u8 *buffer, size_t len) +static int msm_hdmi_bridge_write_spd_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) { + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; u32 buf[7] = {}; u32 val; int i; @@ -139,6 +218,8 @@ static int msm_hdmi_config_spd_infoframe(struct hdmi *hdmi, return -EINVAL; } + msm_hdmi_bridge_clear_spd_infoframe(bridge); + /* checksum gets written together with the body of the frame */ hdmi_write(hdmi, REG_HDMI_GENERIC1_HDR, buffer[0] | @@ -159,9 +240,11 @@ static int msm_hdmi_config_spd_infoframe(struct hdmi *hdmi, return 0; } -static int msm_hdmi_config_hdmi_infoframe(struct hdmi *hdmi, - const u8 *buffer, size_t len) +static int msm_hdmi_bridge_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) { + struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = hdmi_bridge->hdmi; u32 buf[7] = {}; u32 val; int i; @@ -173,6 +256,8 @@ static int msm_hdmi_config_hdmi_infoframe(struct hdmi *hdmi, return -EINVAL; } + msm_hdmi_bridge_clear_hdmi_infoframe(bridge); + /* checksum gets written together with the body of the frame */ hdmi_write(hdmi, REG_HDMI_GENERIC0_HDR, buffer[0] | @@ -194,90 +279,6 @@ static int msm_hdmi_config_hdmi_infoframe(struct hdmi *hdmi, return 0; } -static int msm_hdmi_bridge_clear_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type) -{ - struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); - struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - - switch (type) { - case HDMI_INFOFRAME_TYPE_AVI: - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val &= ~(HDMI_INFOFRAME_CTRL0_AVI_SEND | - HDMI_INFOFRAME_CTRL0_AVI_CONT); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); - - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); - - break; - - case HDMI_INFOFRAME_TYPE_AUDIO: - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val &= ~(HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); - - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); - - break; - - case HDMI_INFOFRAME_TYPE_SPD: - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val &= ~(HDMI_GEN_PKT_CTRL_GENERIC1_SEND | - HDMI_GEN_PKT_CTRL_GENERIC1_CONT | - HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); - - break; - - case HDMI_INFOFRAME_TYPE_VENDOR: - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val &= ~(HDMI_GEN_PKT_CTRL_GENERIC0_SEND | - HDMI_GEN_PKT_CTRL_GENERIC0_CONT | - HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | - HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); - - break; - - default: - drm_dbg_driver(hdmi_bridge->base.dev, "Unsupported infoframe type %x\n", type); - } - - return 0; -} - -static int msm_hdmi_bridge_write_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) -{ - struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); - struct hdmi *hdmi = hdmi_bridge->hdmi; - - msm_hdmi_bridge_clear_infoframe(bridge, type); - - switch (type) { - case HDMI_INFOFRAME_TYPE_AVI: - return msm_hdmi_config_avi_infoframe(hdmi, buffer, len); - case HDMI_INFOFRAME_TYPE_AUDIO: - return msm_hdmi_config_audio_infoframe(hdmi, buffer, len); - case HDMI_INFOFRAME_TYPE_SPD: - return msm_hdmi_config_spd_infoframe(hdmi, buffer, len); - case HDMI_INFOFRAME_TYPE_VENDOR: - return msm_hdmi_config_hdmi_infoframe(hdmi, buffer, len); - default: - drm_dbg_driver(hdmi_bridge->base.dev, "Unsupported infoframe type %x\n", type); - return 0; - } -} - static void msm_hdmi_set_timings(struct hdmi *hdmi, const struct drm_display_mode *mode); @@ -462,8 +463,14 @@ static const struct drm_bridge_funcs msm_hdmi_bridge_funcs = { .hpd_enable = msm_hdmi_hpd_enable, .hpd_disable = msm_hdmi_hpd_disable, .hdmi_tmds_char_rate_valid = msm_hdmi_bridge_tmds_char_rate_valid, - .hdmi_clear_infoframe = msm_hdmi_bridge_clear_infoframe, - .hdmi_write_infoframe = msm_hdmi_bridge_write_infoframe, + .hdmi_clear_audio_infoframe = msm_hdmi_bridge_clear_audio_infoframe, + .hdmi_write_audio_infoframe = msm_hdmi_bridge_write_audio_infoframe, + .hdmi_clear_avi_infoframe = msm_hdmi_bridge_clear_avi_infoframe, + .hdmi_write_avi_infoframe = msm_hdmi_bridge_write_avi_infoframe, + .hdmi_clear_spd_infoframe = msm_hdmi_bridge_clear_spd_infoframe, + .hdmi_write_spd_infoframe = msm_hdmi_bridge_write_spd_infoframe, + .hdmi_clear_hdmi_infoframe = msm_hdmi_bridge_clear_hdmi_infoframe, + .hdmi_write_hdmi_infoframe = msm_hdmi_bridge_write_hdmi_infoframe, .hdmi_audio_prepare = msm_hdmi_bridge_audio_prepare, .hdmi_audio_shutdown = msm_hdmi_bridge_audio_shutdown, }; diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.c b/drivers/gpu/drm/rockchip/rk3066_hdmi.c index ae4a5ac2299a9..6e7d13b139868 100644 --- a/drivers/gpu/drm/rockchip/rk3066_hdmi.c +++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.c @@ -157,35 +157,33 @@ static void rk3066_hdmi_set_power_mode(struct rk3066_hdmi *hdmi, int mode) hdmi->tmdsclk = DEFAULT_PLLA_RATE; } -static int rk3066_hdmi_bridge_clear_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type) +static int rk3066_hdmi_bridge_clear_avi_infoframe(struct drm_bridge *bridge) { struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge); - if (type != HDMI_INFOFRAME_TYPE_AVI) { - drm_err(bridge->dev, "Unsupported infoframe type: %u\n", type); - return 0; - } - hdmi_writeb(hdmi, HDMI_CP_BUF_INDEX, HDMI_INFOFRAME_AVI); return 0; } static int -rk3066_hdmi_bridge_write_infoframe(struct drm_bridge *bridge, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) +rk3066_hdmi_bridge_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + /* FIXME: add support for this InfoFrame */ + + drm_warn_once(bridge->encoder->dev, "HDMI VSI not supported\n"); + + return 0; +} + +static int +rk3066_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) { struct rk3066_hdmi *hdmi = bridge_to_rk3066_hdmi(bridge); ssize_t i; - if (type != HDMI_INFOFRAME_TYPE_AVI) { - drm_err(bridge->dev, "Unsupported infoframe type: %u\n", type); - return 0; - } - - rk3066_hdmi_bridge_clear_infoframe(bridge, type); + rk3066_hdmi_bridge_clear_avi_infoframe(bridge); for (i = 0; i < len; i++) hdmi_writeb(hdmi, HDMI_CP_BUF_ACC_HB0 + i * 4, buffer[i]); @@ -193,6 +191,17 @@ rk3066_hdmi_bridge_write_infoframe(struct drm_bridge *bridge, return 0; } +static int +rk3066_hdmi_bridge_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + rk3066_hdmi_bridge_clear_hdmi_infoframe(bridge); + + /* FIXME: add support for this InfoFrame */ + + return 0; +} + static int rk3066_hdmi_config_video_timing(struct rk3066_hdmi *hdmi, struct drm_display_mode *mode) { @@ -492,8 +501,10 @@ static const struct drm_bridge_funcs rk3066_hdmi_bridge_funcs = { .atomic_disable = rk3066_hdmi_bridge_atomic_disable, .detect = rk3066_hdmi_bridge_detect, .edid_read = rk3066_hdmi_bridge_edid_read, - .hdmi_clear_infoframe = rk3066_hdmi_bridge_clear_infoframe, - .hdmi_write_infoframe = rk3066_hdmi_bridge_write_infoframe, + .hdmi_clear_avi_infoframe = rk3066_hdmi_bridge_clear_avi_infoframe, + .hdmi_write_avi_infoframe = rk3066_hdmi_bridge_write_avi_infoframe, + .hdmi_clear_hdmi_infoframe = rk3066_hdmi_bridge_clear_hdmi_infoframe, + .hdmi_write_hdmi_infoframe = rk3066_hdmi_bridge_write_hdmi_infoframe, .mode_valid = rk3066_hdmi_bridge_mode_valid, }; diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 8bfea1e0a62d9..e7f395ad5db45 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -667,29 +667,113 @@ struct drm_bridge_funcs { unsigned long long tmds_rate); /** - * @hdmi_clear_infoframe: + * @hdmi_clear_avi_infoframe: * * This callback clears the infoframes in the hardware during commit. - * It will be called multiple times, once for every disabled infoframe - * type. * * This callback is optional but it must be implemented by bridges that * set the DRM_BRIDGE_OP_HDMI flag in their &drm_bridge->ops. */ - int (*hdmi_clear_infoframe)(struct drm_bridge *bridge, - enum hdmi_infoframe_type type); + int (*hdmi_clear_avi_infoframe)(struct drm_bridge *bridge); + + /** + * @hdmi_write_avi_infoframe: + * + * Program the infoframe into the hardware. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI flag in their &drm_bridge->ops. + */ + int (*hdmi_write_avi_infoframe)(struct drm_bridge *bridge, + const u8 *buffer, size_t len); + + /** + * @hdmi_clear_hdmi_infoframe: + * + * This callback clears the infoframes in the hardware during commit. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI flag in their &drm_bridge->ops. + */ + int (*hdmi_clear_hdmi_infoframe)(struct drm_bridge *bridge); + /** - * @hdmi_write_infoframe: + * @hdmi_write_hdmi_infoframe: * - * Program the infoframe into the hardware. It will be called multiple - * times, once for every updated infoframe type. + * Program the infoframe into the hardware. * * This callback is optional but it must be implemented by bridges that * set the DRM_BRIDGE_OP_HDMI flag in their &drm_bridge->ops. */ - int (*hdmi_write_infoframe)(struct drm_bridge *bridge, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len); + int (*hdmi_write_hdmi_infoframe)(struct drm_bridge *bridge, + const u8 *buffer, size_t len); + + /** + * @hdmi_clear_hdr_drm_infoframe: + * + * This callback clears the infoframes in the hardware during commit. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME flag in their + * &drm_bridge->ops. + */ + int (*hdmi_clear_hdr_drm_infoframe)(struct drm_bridge *bridge); + + /** + * @hdmi_write_hdr_drm_infoframe: + * + * Program the infoframe into the hardware. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME flag in their + * &drm_bridge->ops. + */ + int (*hdmi_write_hdr_drm_infoframe)(struct drm_bridge *bridge, + const u8 *buffer, size_t len); + + /** + * @hdmi_clear_spd_infoframe: + * + * This callback clears the infoframes in the hardware during commit. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME flag in their + * &drm_bridge->ops. + */ + int (*hdmi_clear_spd_infoframe)(struct drm_bridge *bridge); + + /** + * @hdmi_write_spd_infoframe: + * + * Program the infoframe into the hardware. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME flag in their + * &drm_bridge->ops. + */ + int (*hdmi_write_spd_infoframe)(struct drm_bridge *bridge, + const u8 *buffer, size_t len); + + /** + * @hdmi_clear_audio_infoframe: + * + * This callback clears the infoframes in the hardware during commit. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI_AUDIO flag in their &drm_bridge->ops. + */ + int (*hdmi_clear_audio_infoframe)(struct drm_bridge *bridge); + + /** + * @hdmi_write_audio_infoframe: + * + * Program the infoframe into the hardware. + * + * This callback is optional but it must be implemented by bridges that + * set the DRM_BRIDGE_OP_HDMI_AUDIO flag in their &drm_bridge->ops. + */ + int (*hdmi_write_audio_infoframe)(struct drm_bridge *bridge, + const u8 *buffer, size_t len); /** * @hdmi_audio_startup: @@ -945,7 +1029,11 @@ enum drm_bridge_ops { /** * @DRM_BRIDGE_OP_HDMI: The bridge provides HDMI connector operations, * including infoframes support. Bridges that set this flag must - * implement the &drm_bridge_funcs->write_infoframe callback. + * provide HDMI-related information and implement the + * &drm_bridge_funcs->clear_avi_infoframe, + * &drm_bridge_funcs->write_avi_infoframe, + * &drm_bridge_funcs->clear_hdmi_infoframe and + * &drm_bridge_funcs->write_hdmi_infoframe callbacks. * * Note: currently there can be at most one bridge in a chain that sets * this bit. This is to simplify corresponding glue code in connector @@ -957,6 +1045,9 @@ enum drm_bridge_ops { * Bridges that set this flag must implement the * &drm_bridge_funcs->hdmi_audio_prepare and * &drm_bridge_funcs->hdmi_audio_shutdown callbacks. + * If the bridge implements @DRM_BRIDGE_OP_HDMI, it also must implement + * &drm_bridge_funcs->hdmi_write_audio_infoframe and + * &drm_bridge_funcs->hdmi_cleaer_audio_infoframe callbacks. * * Note: currently there can be at most one bridge in a chain that sets * this bit. This is to simplify corresponding glue code in connector @@ -988,6 +1079,18 @@ enum drm_bridge_ops { * to be present. */ DRM_BRIDGE_OP_HDMI_CEC_ADAPTER = BIT(8), + /** + * @DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME: The bridge supports + * &drm_bridge_funcs->hdmi_write_hdr_drm_infoframe and + * &drm_bridge_funcs->hdmi_clear_hdr_drm_infoframe callbacks. + */ + DRM_BRIDGE_OP_HDMI_HDR_DRM_INFOFRAME = BIT(9), + /** + * @DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME: The bridge supports + * &drm_bridge_funcs->hdmi_write_spd_infoframe and + * &drm_bridge_funcs->hdmi_clear_spd_infoframe callbacks. + */ + DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME = BIT(10), }; /**