* [PATCH 1/9] media: synopsys: hdmirx: add media device infrastructure
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 13:44 ` [PATCH 2/9] media: synopsys: hdmirx: add media entity for video capture device Sascha Hauer
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
From: Gerald Loacker <gerald.loacker@wolfvision.net>
Initialize the media device to enable integration with HDMI bridge
devices in the video capture pipeline. This provides the foundation
for representing the hardware topology through the media controller
framework.
Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
index 25f8ca0d6d946..f51a1619b71ec 100644
--- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
@@ -31,6 +31,7 @@
#include <linux/workqueue.h>
#include <media/cec.h>
+#include <media/media-device.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
@@ -123,6 +124,7 @@ struct snps_hdmirx_dev {
struct device *dev;
struct hdmirx_stream stream;
struct v4l2_device v4l2_dev;
+ struct media_device mdev;
struct v4l2_ctrl_handler hdl;
struct v4l2_ctrl *detect_tx_5v_ctrl;
struct v4l2_ctrl *rgb_range;
@@ -2742,6 +2744,11 @@ static int hdmirx_probe(struct platform_device *pdev)
goto err_hdl;
}
+ hdmirx_dev->mdev.dev = dev;
+ strscpy(hdmirx_dev->mdev.model, "snps-hdmirx", sizeof(hdmirx_dev->mdev.model));
+ media_device_init(&hdmirx_dev->mdev);
+ hdmirx_dev->v4l2_dev.mdev = &hdmirx_dev->mdev;
+
stream = &hdmirx_dev->stream;
stream->hdmirx_dev = hdmirx_dev;
ret = hdmirx_register_stream_vdev(stream);
@@ -2771,6 +2778,7 @@ static int hdmirx_probe(struct platform_device *pdev)
vb2_video_unregister_device(&hdmirx_dev->stream.vdev);
err_unreg_v4l2_dev:
v4l2_device_unregister(&hdmirx_dev->v4l2_dev);
+ media_device_cleanup(&hdmirx_dev->mdev);
err_hdl:
v4l2_ctrl_handler_free(&hdmirx_dev->hdl);
err_pm:
@@ -2794,6 +2802,7 @@ static void hdmirx_remove(struct platform_device *pdev)
vb2_video_unregister_device(&hdmirx_dev->stream.vdev);
v4l2_ctrl_handler_free(&hdmirx_dev->hdl);
v4l2_device_unregister(&hdmirx_dev->v4l2_dev);
+ media_device_cleanup(&hdmirx_dev->mdev);
/* touched by hdmirx_disable()->hdmirx_plugout() */
hdmirx_dev->rgb_range = NULL;
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 2/9] media: synopsys: hdmirx: add media entity for video capture device
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
2026-09-23 13:44 ` [PATCH 1/9] media: synopsys: hdmirx: add media device infrastructure Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 13:44 ` [PATCH 3/9] dt-bindings: media: snps,dw-hdmi-rx: add the source port Sascha Hauer
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
From: Gerald Loacker <gerald.loacker@wolfvision.net>
Register the video device as a media entity with a sink pad to integrate
it into the media controller topology. This allows the capture device to
be properly represented in the media graph and enables userspace to
discover the device capabilities through the MC API.
The sink pad indicates that this entity receives data (HDMI video stream)
which is then made available through the V4L2 video capture interface.
Initialize the pad before registering the video device. The previous patch
gave v4l2_dev an mdev, so video_register_device() already registers the
entity, and doing it the other way round would hand
media_device_register_entity() an entity with no pads yet.
Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
.../media/platform/synopsys/hdmirx/snps_hdmirx.c | 26 ++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
index f51a1619b71ec..11a9b16586a57 100644
--- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
@@ -125,6 +125,7 @@ struct snps_hdmirx_dev {
struct hdmirx_stream stream;
struct v4l2_device v4l2_dev;
struct media_device mdev;
+ struct media_pad pad;
struct v4l2_ctrl_handler hdl;
struct v4l2_ctrl *detect_tx_5v_ctrl;
struct v4l2_ctrl *rgb_range;
@@ -2751,15 +2752,29 @@ static int hdmirx_probe(struct platform_device *pdev)
stream = &hdmirx_dev->stream;
stream->hdmirx_dev = hdmirx_dev;
+
+ hdmirx_dev->pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&stream->vdev.entity, 1, &hdmirx_dev->pad);
+ if (ret) {
+ dev_err_probe(dev, ret, "media entity pads initialization failed\n");
+ goto err_unreg_v4l2_dev;
+ }
+
ret = hdmirx_register_stream_vdev(stream);
if (ret < 0) {
dev_err_probe(dev, ret, "video device registration failed\n");
- goto err_unreg_v4l2_dev;
+ goto err_cleanup_entity;
+ }
+
+ ret = media_device_register(&hdmirx_dev->mdev);
+ if (ret) {
+ dev_err_probe(dev, ret, "media device registration failed\n");
+ goto err_unreg_video_dev;
}
ret = hdmirx_register_cec(hdmirx_dev, pdev);
if (ret)
- goto err_unreg_video_dev;
+ goto err_unreg_media;
hdmirx_load_default_edid(hdmirx_dev);
@@ -2774,8 +2789,12 @@ static int hdmirx_probe(struct platform_device *pdev)
return 0;
+err_unreg_media:
+ media_device_unregister(&hdmirx_dev->mdev);
err_unreg_video_dev:
vb2_video_unregister_device(&hdmirx_dev->stream.vdev);
+err_cleanup_entity:
+ media_entity_cleanup(&hdmirx_dev->stream.vdev.entity);
err_unreg_v4l2_dev:
v4l2_device_unregister(&hdmirx_dev->v4l2_dev);
media_device_cleanup(&hdmirx_dev->mdev);
@@ -2799,7 +2818,10 @@ static void hdmirx_remove(struct platform_device *pdev)
hdmirx_disable_irq(dev);
+ media_device_unregister(&hdmirx_dev->mdev);
+
vb2_video_unregister_device(&hdmirx_dev->stream.vdev);
+ media_entity_cleanup(&hdmirx_dev->stream.vdev.entity);
v4l2_ctrl_handler_free(&hdmirx_dev->hdl);
v4l2_device_unregister(&hdmirx_dev->v4l2_dev);
media_device_cleanup(&hdmirx_dev->mdev);
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 3/9] dt-bindings: media: snps,dw-hdmi-rx: add the source port
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
2026-09-23 13:44 ` [PATCH 1/9] media: synopsys: hdmirx: add media device infrastructure Sascha Hauer
2026-09-23 13:44 ` [PATCH 2/9] media: synopsys: hdmirx: add media entity for video capture device Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 16:34 ` Conor Dooley
2026-09-23 13:44 ` [PATCH 4/9] media: synopsys: hdmirx: add async subdevice support Sascha Hauer
` (5 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
Describe the OF graph port connecting the receiver to an HDMI bridge in
front of it. Boards that wire the HDMI connector straight to the receiver
leave it out, so it is not required.
The schema sets additionalProperties: false, so without this a device
tree carrying the endpoint does not validate at all.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
index b7f6c87d0e06a..7e66dcbdfe2cb 100644
--- a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
+++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
@@ -66,6 +66,14 @@ properties:
description: GPIO specifier for HPD.
maxItems: 1
+ port:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Connection to the HDMI source. Present when an HDMI bridge sits in
+ front of this receiver instead of the connector being wired straight
+ to it. The bridge is bound as a v4l2 subdevice and appears in the
+ media graph.
+
rockchip,grf:
$ref: /schemas/types.yaml#/definitions/phandle
description:
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 3/9] dt-bindings: media: snps,dw-hdmi-rx: add the source port
2026-09-23 13:44 ` [PATCH 3/9] dt-bindings: media: snps,dw-hdmi-rx: add the source port Sascha Hauer
@ 2026-09-23 16:34 ` Conor Dooley
0 siblings, 0 replies; 13+ messages in thread
From: Conor Dooley @ 2026-09-23 16:34 UTC (permalink / raw)
To: Sascha Hauer
Cc: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Gerald Loacker,
Lucas Sinn, linux-media, devicetree, kernel, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1620 bytes --]
On Wed, Sep 23, 2026 at 03:44:42PM +0200, Sascha Hauer wrote:
> Describe the OF graph port connecting the receiver to an HDMI bridge in
> front of it. Boards that wire the HDMI connector straight to the receiver
> leave it out, so it is not required.
>
> The schema sets additionalProperties: false, so without this a device
> tree carrying the endpoint does not validate at all.
>
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
> ---
> Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> index b7f6c87d0e06a..7e66dcbdfe2cb 100644
> --- a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> +++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> @@ -66,6 +66,14 @@ properties:
> description: GPIO specifier for HPD.
> maxItems: 1
>
> + port:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + Connection to the HDMI source. Present when an HDMI bridge sits in
> + front of this receiver instead of the connector being wired straight
> + to it.
> The bridge is bound as a v4l2 subdevice and appears in the
> + media graph.
This sounds like OS-specific info that shouldn't be here.
pw-bot: changes-requested
Thanks,
Conor.
> +
> rockchip,grf:
> $ref: /schemas/types.yaml#/definitions/phandle
> description:
>
> --
> 2.47.3
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 4/9] media: synopsys: hdmirx: add async subdevice support
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
` (2 preceding siblings ...)
2026-09-23 13:44 ` [PATCH 3/9] dt-bindings: media: snps,dw-hdmi-rx: add the source port Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 13:44 ` [PATCH 5/9] media: synopsys: hdmirx: give the signal lock wait a real timeout Sascha Hauer
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
From: Gerald Loacker <gerald.loacker@wolfvision.net>
Add V4L2 async notifier support to enable dynamic binding of connected
subdevices in the video pipeline. The driver parses device-tree endpoints
to discover and register video bridge subdevices.
When a subdevice is bound, a media link is created between its output pad
and the HDMI RX sink pad. Subdevice nodes are registered once binding
completes, making them accessible from userspace.
If no endpoint is defined in the device tree, the driver operates in
standalone mode and registers its subdevice nodes directly, maintaining
backward compatibility with simpler configurations.
The notifier itself is unregistered on the way out whether or not a
subdevice ever bound. Only v4l2_async_nf_unregister() takes the notifier
off the global list, so cleaning up without it would leave an entry
pointing into memory devres is about to free. Both calls are no-ops when
there was no endpoint to watch.
Ask the bound subdevice which of its pads faces us rather than assuming
the second one. media_create_pad_link() warns and fails on an index the
entity does not have, which a bridge with a single pad would hit.
V4L2_ASYNC has to be selected now that the driver calls the notifier
helpers. v4l2-async.h has no stubs for them, and nothing else the driver
already selects pulls the symbol in.
Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/media/platform/synopsys/hdmirx/Kconfig | 1 +
.../media/platform/synopsys/hdmirx/snps_hdmirx.c | 90 +++++++++++++++++++++-
2 files changed, 90 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/synopsys/hdmirx/Kconfig b/drivers/media/platform/synopsys/hdmirx/Kconfig
index 4321f985f6320..598c303eba8d0 100644
--- a/drivers/media/platform/synopsys/hdmirx/Kconfig
+++ b/drivers/media/platform/synopsys/hdmirx/Kconfig
@@ -5,6 +5,7 @@ config VIDEO_SYNOPSYS_HDMIRX
depends on ARCH_ROCKCHIP || COMPILE_TEST
depends on VIDEO_DEV
select MEDIA_CONTROLLER
+ select V4L2_ASYNC
select VIDEO_V4L2_SUBDEV_API
select VIDEOBUF2_DMA_CONTIG
select CEC_CORE
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
index 11a9b16586a57..0c7cac25bf694 100644
--- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
@@ -20,6 +20,7 @@
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/pinctrl/consumer.h>
@@ -32,12 +33,14 @@
#include <media/cec.h>
#include <media/media-device.h>
+#include <media/v4l2-async.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fh.h>
+#include <media/v4l2-fwnode.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-v4l2.h>
@@ -126,6 +129,7 @@ struct snps_hdmirx_dev {
struct v4l2_device v4l2_dev;
struct media_device mdev;
struct media_pad pad;
+ struct v4l2_async_notifier notifier;
struct v4l2_ctrl_handler hdl;
struct v4l2_ctrl *detect_tx_5v_ctrl;
struct v4l2_ctrl *rgb_range;
@@ -2649,6 +2653,48 @@ static int hdmirx_register_cec(struct snps_hdmirx_dev *hdmirx_dev,
return 0;
}
+static int hdmirx_fwnode_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *asc)
+{
+ struct snps_hdmirx_dev *hdmirx_dev =
+ container_of(notifier, struct snps_hdmirx_dev, notifier);
+ int source_pad;
+ int ret;
+
+ source_pad = media_entity_get_fwnode_pad(&subdev->entity,
+ asc->match.fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (source_pad < 0) {
+ dev_err(hdmirx_dev->dev, "%s has no source pad for %pfw: %d\n",
+ subdev->name, asc->match.fwnode, source_pad);
+ return source_pad;
+ }
+
+ ret = media_create_pad_link(&subdev->entity, source_pad,
+ &hdmirx_dev->stream.vdev.entity, 0,
+ MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+ if (ret) {
+ dev_err(hdmirx_dev->dev, "Failed to create media link: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hdmirx_fwnode_complete(struct v4l2_async_notifier *notifier)
+{
+ struct snps_hdmirx_dev *hdmirx_dev =
+ container_of(notifier, struct snps_hdmirx_dev, notifier);
+
+ return v4l2_device_register_subdev_nodes(&hdmirx_dev->v4l2_dev);
+}
+
+static const struct v4l2_async_notifier_operations hdmirx_async_ops = {
+ .bound = hdmirx_fwnode_bound,
+ .complete = hdmirx_fwnode_complete,
+};
+
static int hdmirx_probe(struct platform_device *pdev)
{
struct snps_hdmirx_dev *hdmirx_dev;
@@ -2656,6 +2702,8 @@ static int hdmirx_probe(struct platform_device *pdev)
struct v4l2_ctrl_handler *hdl;
struct hdmirx_stream *stream;
struct v4l2_device *v4l2_dev;
+ struct device_node *ep_node;
+ struct v4l2_async_connection *asc;
int ret;
hdmirx_dev = devm_kzalloc(dev, sizeof(*hdmirx_dev), GFP_KERNEL);
@@ -2772,9 +2820,38 @@ static int hdmirx_probe(struct platform_device *pdev)
goto err_unreg_video_dev;
}
+ ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
+ if (ep_node) {
+ v4l2_async_nf_init(&hdmirx_dev->notifier, &hdmirx_dev->v4l2_dev);
+ hdmirx_dev->notifier.ops = &hdmirx_async_ops;
+
+ asc = v4l2_async_nf_add_fwnode_remote(&hdmirx_dev->notifier,
+ of_fwnode_handle(ep_node),
+ struct v4l2_async_connection);
+ of_node_put(ep_node);
+
+ if (IS_ERR(asc)) {
+ ret = PTR_ERR(asc);
+ dev_err_probe(dev, ret, "Failed to add remote fwnode\n");
+ goto err_unreg_media;
+ }
+
+ ret = v4l2_async_nf_register(&hdmirx_dev->notifier);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register async notifier\n");
+ goto err_cleanup_notifier;
+ }
+ } else {
+ ret = v4l2_device_register_subdev_nodes(&hdmirx_dev->v4l2_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register subdev nodes\n");
+ goto err_unreg_media;
+ }
+ }
+
ret = hdmirx_register_cec(hdmirx_dev, pdev);
if (ret)
- goto err_unreg_media;
+ goto err_unreg_notifier;
hdmirx_load_default_edid(hdmirx_dev);
@@ -2789,6 +2866,10 @@ static int hdmirx_probe(struct platform_device *pdev)
return 0;
+err_unreg_notifier:
+ v4l2_async_nf_unregister(&hdmirx_dev->notifier);
+err_cleanup_notifier:
+ v4l2_async_nf_cleanup(&hdmirx_dev->notifier);
err_unreg_media:
media_device_unregister(&hdmirx_dev->mdev);
err_unreg_video_dev:
@@ -2818,6 +2899,13 @@ static void hdmirx_remove(struct platform_device *pdev)
hdmirx_disable_irq(dev);
+ /*
+ * Both are no-ops when there was no endpoint to watch: the notifier is
+ * zeroed and neither v4l2_dev nor waiting_list is set.
+ */
+ v4l2_async_nf_unregister(&hdmirx_dev->notifier);
+ v4l2_async_nf_cleanup(&hdmirx_dev->notifier);
+
media_device_unregister(&hdmirx_dev->mdev);
vb2_video_unregister_device(&hdmirx_dev->stream.vdev);
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 5/9] media: synopsys: hdmirx: give the signal lock wait a real timeout
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
` (3 preceding siblings ...)
2026-09-23 13:44 ` [PATCH 4/9] media: synopsys: hdmirx: add async subdevice support Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 13:44 ` [PATCH 6/9] dt-bindings: media: snps,dw-hdmi-rx: make hpd-gpios optional Sascha Hauer
` (3 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
From: Gerald Loacker <gerald.loacker@wolfvision.net>
hdmirx_wait_signal_lock() bounded itself by a count of 300 iterations.
How long that came to depended on the debounce inside
tx_5v_power_present(), which the loop calls once per iteration and which
sleeps about 7ms of it, so the effective timeout was around two seconds
by accident rather than by design.
That accident is about to go away. A later patch in this series takes the
5V state from an upstream subdev where the board has no hpd-gpios, which
leaves tx_5v_power_present() a plain read of a flag costing microseconds.
The same 300 iterations would then be over in a few milliseconds and the
wait would give up almost immediately.
Poll on a fixed 10ms interval against an explicit three second deadline.
Check the deadline after the 5V test so a disconnect is still reported as
-ENOLINK rather than a timeout, and report the timeout as -ETIMEDOUT; the
sole caller only tests for non-zero, so neither is a change in behaviour.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
.../media/platform/synopsys/hdmirx/snps_hdmirx.c | 40 +++++++++++++++-------
1 file changed, 28 insertions(+), 12 deletions(-)
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
index 0c7cac25bf694..8c6b04425c8a9 100644
--- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
@@ -2133,13 +2133,25 @@ static irqreturn_t hdmirx_dma_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+/*
+ * Wait for the HDMI signal to lock: TMDS clock detection and ratio
+ * configuration, then PHY and CMU lock, then DMA lock.
+ *
+ * The wait used to be bounded by a count of 300 iterations. What that came
+ * to in wall clock time was decided by the debounce inside
+ * tx_5v_power_present(), which is called once per iteration and sleeps
+ * about 7ms of it, so the real timeout was an accident of how 5V happens to
+ * be sensed. Poll on a fixed interval against an explicit deadline instead.
+ */
static int hdmirx_wait_signal_lock(struct snps_hdmirx_dev *hdmirx_dev)
{
struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
u32 mu_status, scdc_status, dma_st10, cmu_st;
- u32 i;
+ unsigned long timeout;
+
+ timeout = jiffies + msecs_to_jiffies(3000);
- for (i = 0; i < 300; i++) {
+ for (;;) {
mu_status = hdmirx_readl(hdmirx_dev, MAINUNIT_STATUS);
scdc_status = hdmirx_readl(hdmirx_dev, SCDC_REGBANK_STATUS3);
dma_st10 = hdmirx_readl(hdmirx_dev, DMA_STATUS10);
@@ -2153,21 +2165,25 @@ static int hdmirx_wait_signal_lock(struct snps_hdmirx_dev *hdmirx_dev)
if (!tx_5v_power_present(hdmirx_dev)) {
v4l2_dbg(1, debug, v4l2_dev,
"%s: HDMI pull out, return\n", __func__);
- return -1;
+ return -ENOLINK;
}
- hdmirx_tmds_clk_ratio_config(hdmirx_dev);
- }
+ if (time_after(jiffies, timeout)) {
+ v4l2_err(v4l2_dev,
+ "%s: signal not lock, tmds_clk_ratio:%d\n",
+ __func__, hdmirx_dev->tmds_clk_ratio);
+ v4l2_err(v4l2_dev,
+ "%s: mu_st:%#x, scdc_st:%#x, dma_st10:%#x, cmu_st:%#x\n",
+ __func__, mu_status, scdc_status, dma_st10,
+ cmu_st);
+ return -ETIMEDOUT;
+ }
- if (i == 300) {
- v4l2_err(v4l2_dev, "%s: signal not lock, tmds_clk_ratio:%d\n",
- __func__, hdmirx_dev->tmds_clk_ratio);
- v4l2_err(v4l2_dev, "%s: mu_st:%#x, scdc_st:%#x, dma_st10:%#x\n",
- __func__, mu_status, scdc_status, dma_st10);
- return -1;
+ hdmirx_tmds_clk_ratio_config(hdmirx_dev);
+ usleep_range(10000, 11000);
}
- v4l2_dbg(1, debug, v4l2_dev, "%s: signal lock ok, i:%d\n", __func__, i);
+ v4l2_dbg(1, debug, v4l2_dev, "%s: signal lock ok\n", __func__);
hdmirx_writel(hdmirx_dev, GLOBAL_SWRESET_REQUEST, DATAPATH_SWRESETREQ);
reinit_completion(&hdmirx_dev->avi_pkt_rcv);
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 6/9] dt-bindings: media: snps,dw-hdmi-rx: make hpd-gpios optional
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
` (4 preceding siblings ...)
2026-09-23 13:44 ` [PATCH 5/9] media: synopsys: hdmirx: give the signal lock wait a real timeout Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 16:46 ` Conor Dooley
2026-09-23 13:44 ` [PATCH 7/9] media: synopsys: hdmirx: skip the 5V detect interrupt without a GPIO Sascha Hauer
` (2 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
hpd-gpios is the +5V line of the HDMI connector, which the source drives
to announce itself. On a board whose HDMI connector belongs to a bridge
placed in front of this receiver, that line goes to the bridge and there
is nothing for the receiver to sense, so the property cannot be
provided.
Drop it from the required list and describe what it actually carries.
The name is unfortunate: the hot plug detect line the receiver drives
back at the source is a controller register, not this GPIO.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
index 7e66dcbdfe2cb..9c6c377417c0b 100644
--- a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
+++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
@@ -63,7 +63,12 @@ properties:
maxItems: 1
hpd-gpios:
- description: GPIO specifier for HPD.
+ description:
+ The +5V line of the HDMI connector, which the source drives to announce
+ itself. Despite the name this is an input to the receiver; the hot plug
+ detect line the receiver drives back at the source is handled inside the
+ controller. Absent when the connector belongs to a bridge in front of
+ this receiver rather than to the receiver itself.
maxItems: 1
port:
@@ -96,7 +101,6 @@ required:
- power-domains
- resets
- pinctrl-0
- - hpd-gpios
additionalProperties: false
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 6/9] dt-bindings: media: snps,dw-hdmi-rx: make hpd-gpios optional
2026-09-23 13:44 ` [PATCH 6/9] dt-bindings: media: snps,dw-hdmi-rx: make hpd-gpios optional Sascha Hauer
@ 2026-09-23 16:46 ` Conor Dooley
2026-09-24 6:12 ` Sascha Hauer
0 siblings, 1 reply; 13+ messages in thread
From: Conor Dooley @ 2026-09-23 16:46 UTC (permalink / raw)
To: Sascha Hauer
Cc: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Gerald Loacker,
Lucas Sinn, linux-media, devicetree, kernel, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 2122 bytes --]
On Wed, Sep 23, 2026 at 03:44:45PM +0200, Sascha Hauer wrote:
> hpd-gpios is the +5V line of the HDMI connector, which the source drives
> to announce itself. On a board whose HDMI connector belongs to a bridge
> placed in front of this receiver, that line goes to the bridge and there
> is nothing for the receiver to sense, so the property cannot be
> provided.
>
> Drop it from the required list and describe what it actually carries.
> The name is unfortunate: the hot plug detect line the receiver drives
> back at the source is a controller register, not this GPIO.
>
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
> ---
> Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> index 7e66dcbdfe2cb..9c6c377417c0b 100644
> --- a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> +++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> @@ -63,7 +63,12 @@ properties:
> maxItems: 1
>
> hpd-gpios:
> - description: GPIO specifier for HPD.
> + description:
> + The +5V line of the HDMI connector, which the source drives to announce
> + itself. Despite the name this is an input to the receiver; the hot plug
> + detect line the receiver drives back at the source is handled inside the
> + controller. Absent when the connector belongs to a bridge in front of
> + this receiver rather than to the receiver itself.
> maxItems: 1
>
> port:
> @@ -96,7 +101,6 @@ required:
> - power-domains
> - resets
> - pinctrl-0
> - - hpd-gpios
Would it be a good idea to require either this or ports, rather than
remove it? Just to keep the coverage up since I assume the feature is
required without the bridge. Perhaps that's not the case and this is
genuinely not needed.
>
> additionalProperties: false
>
>
> --
> 2.47.3
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 6/9] dt-bindings: media: snps,dw-hdmi-rx: make hpd-gpios optional
2026-09-23 16:46 ` Conor Dooley
@ 2026-09-24 6:12 ` Sascha Hauer
0 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-24 6:12 UTC (permalink / raw)
To: Conor Dooley
Cc: Sascha Hauer, Dmitry Osipenko, Mauro Carvalho Chehab,
Hans Verkuil, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel
On 2026-09-23 17:46, Conor Dooley wrote:
> On Wed, Sep 23, 2026 at 03:44:45PM +0200, Sascha Hauer wrote:
> > hpd-gpios is the +5V line of the HDMI connector, which the source drives
> > to announce itself. On a board whose HDMI connector belongs to a bridge
> > placed in front of this receiver, that line goes to the bridge and there
> > is nothing for the receiver to sense, so the property cannot be
> > provided.
> >
> > Drop it from the required list and describe what it actually carries.
> > The name is unfortunate: the hot plug detect line the receiver drives
> > back at the source is a controller register, not this GPIO.
> >
> > Assisted-by: Claude:claude-opus-5
> > Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
> > ---
> > Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml | 8 ++++++--
> > 1 file changed, 6 insertions(+), 2 deletions(-)
> >
> > diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> > index 7e66dcbdfe2cb..9c6c377417c0b 100644
> > --- a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> > +++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
> > @@ -63,7 +63,12 @@ properties:
> > maxItems: 1
> >
> > hpd-gpios:
> > - description: GPIO specifier for HPD.
> > + description:
> > + The +5V line of the HDMI connector, which the source drives to announce
> > + itself. Despite the name this is an input to the receiver; the hot plug
> > + detect line the receiver drives back at the source is handled inside the
> > + controller. Absent when the connector belongs to a bridge in front of
> > + this receiver rather than to the receiver itself.
> > maxItems: 1
> >
> > port:
> > @@ -96,7 +101,6 @@ required:
> > - power-domains
> > - resets
> > - pinctrl-0
> > - - hpd-gpios
>
> Would it be a good idea to require either this or ports, rather than
> remove it? Just to keep the coverage up since I assume the feature is
> required without the bridge. Perhaps that's not the case and this is
> genuinely not needed.
Yes, makes sense. One or the other should be required. Will change next
round.
Sascha
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 7/9] media: synopsys: hdmirx: skip the 5V detect interrupt without a GPIO
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
` (5 preceding siblings ...)
2026-09-23 13:44 ` [PATCH 6/9] dt-bindings: media: snps,dw-hdmi-rx: make hpd-gpios optional Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 13:44 ` [PATCH 8/9] media: v4l2-subdev: notify the bridge when the source power changes Sascha Hauer
2026-09-23 13:44 ` [PATCH 9/9] media: synopsys: hdmirx: get the 5V state from the upstream subdev Sascha Hauer
8 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
From: Gerald Loacker <gerald.loacker@wolfvision.net>
The hpd GPIO is fetched with devm_gpiod_get_optional(), so a board that
does not wire 5V detect to the receiver leaves detect_5v_gpio NULL.
hdmirx_setup_irq() then calls gpiod_to_irq() on it anyway, which returns
-EINVAL for a NULL descriptor, and probe fails. The GPIO is only
optional as far as the fetch goes.
Set the interrupt up only when the GPIO is there. det_irq then keeps the
zero devm_kzalloc() gave it, so hdmirx_enable_irq() and
hdmirx_disable_irq() skip it as well.
Nothing else has to change for such a board to probe: 5V is then never
reported as present, which is what gpiod_get_value_cansleep() on a NULL
descriptor already returns. Getting the state from somewhere else is a
separate matter.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
.../media/platform/synopsys/hdmirx/snps_hdmirx.c | 34 ++++++++++++----------
1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
index 8c6b04425c8a9..23b4dd853be57 100644
--- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
@@ -2525,7 +2525,8 @@ static void hdmirx_disable_irq(struct device *dev)
{
struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
- disable_irq(hdmirx_dev->det_irq);
+ if (hdmirx_dev->det_irq > 0)
+ disable_irq(hdmirx_dev->det_irq);
disable_irq(hdmirx_dev->dma_irq);
disable_irq(hdmirx_dev->hdmi_irq);
@@ -2539,7 +2540,8 @@ static void hdmirx_enable_irq(struct device *dev)
enable_irq(hdmirx_dev->hdmi_irq);
enable_irq(hdmirx_dev->dma_irq);
- enable_irq(hdmirx_dev->det_irq);
+ if (hdmirx_dev->det_irq > 0)
+ enable_irq(hdmirx_dev->det_irq);
queue_delayed_work(system_dfl_wq,
&hdmirx_dev->delayed_work_hotplug,
@@ -2623,21 +2625,23 @@ static int hdmirx_setup_irq(struct snps_hdmirx_dev *hdmirx_dev,
return ret;
}
- irq = gpiod_to_irq(hdmirx_dev->detect_5v_gpio);
- if (irq < 0) {
- dev_err_probe(dev, irq, "failed to get hdmirx-5v irq\n");
- return irq;
- }
+ if (hdmirx_dev->detect_5v_gpio) {
+ irq = gpiod_to_irq(hdmirx_dev->detect_5v_gpio);
+ if (irq < 0) {
+ dev_err_probe(dev, irq, "failed to get hdmirx-5v irq\n");
+ return irq;
+ }
- irq_set_status_flags(irq, IRQ_NOAUTOEN);
+ irq_set_status_flags(irq, IRQ_NOAUTOEN);
- hdmirx_dev->det_irq = irq;
- ret = devm_request_irq(dev, irq, hdmirx_5v_det_irq_handler,
- IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
- "rk_hdmirx-5v", hdmirx_dev);
- if (ret) {
- dev_err_probe(dev, ret, "failed to request hdmirx-5v irq\n");
- return ret;
+ hdmirx_dev->det_irq = irq;
+ ret = devm_request_irq(dev, irq, hdmirx_5v_det_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "rk_hdmirx-5v", hdmirx_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to request hdmirx-5v irq\n");
+ return ret;
+ }
}
return 0;
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 8/9] media: v4l2-subdev: notify the bridge when the source power changes
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
` (6 preceding siblings ...)
2026-09-23 13:44 ` [PATCH 7/9] media: synopsys: hdmirx: skip the 5V detect interrupt without a GPIO Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
2026-09-23 13:44 ` [PATCH 9/9] media: synopsys: hdmirx: get the 5V state from the upstream subdev Sascha Hauer
8 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
Whether a source is driving the connector's +5V line is published as
V4L2_CID_DV_RX_POWER_PRESENT and nowhere else. adv7604, adv7842,
tc358743 and tda1997x all set that control from their cable detect
interrupt and stop there, so a driver that has to act on the change in
hardware, rather than hand the value to userspace, has nothing to hook.
The in-kernel channel already exists for the neighbouring events. A
format change goes out through v4l2_subdev_notify_event() as
V4L2_EVENT_SOURCE_CHANGE, and adv7604 sends a private ADV76XX_HOTPLUG
when the bridge has to drive HPD back out at the connector, which cobalt
consumes. Only the source power state is missing.
Add a generic notification for it. The argument carries the same
per-input mask as the control, so a device in front of a receiver can
pass on what it sees without the receiver having to reach into its
control handler.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
include/media/v4l2-subdev.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index d256b7ec8f848..407b353fa943d 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -29,6 +29,14 @@
#define V4L2_DEVICE_NOTIFY_EVENT _IOW('v', 2, struct v4l2_event)
+/*
+ * The source power state a receiver publishes as
+ * V4L2_CID_DV_RX_POWER_PRESENT has changed. The argument points at an
+ * unsigned int holding the same per-input mask as that control, so zero
+ * means no input sees a source.
+ */
+#define V4L2_DEVICE_NOTIFY_RX_POWER_PRESENT _IOW('v', 3, unsigned int)
+
struct v4l2_device;
struct v4l2_ctrl_handler;
struct v4l2_event;
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 9/9] media: synopsys: hdmirx: get the 5V state from the upstream subdev
2026-09-23 13:44 [PATCH 0/9] media: synopsys: hdmirx: support an HDMI bridge in front of the receiver Sascha Hauer
` (7 preceding siblings ...)
2026-09-23 13:44 ` [PATCH 8/9] media: v4l2-subdev: notify the bridge when the source power changes Sascha Hauer
@ 2026-09-23 13:44 ` Sascha Hauer
8 siblings, 0 replies; 13+ messages in thread
From: Sascha Hauer @ 2026-09-23 13:44 UTC (permalink / raw)
To: Dmitry Osipenko, Mauro Carvalho Chehab, Hans Verkuil,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Gerald Loacker, Lucas Sinn, linux-media, devicetree, kernel,
linux-kernel, Sascha Hauer
From: Gerald Loacker <gerald.loacker@wolfvision.net>
On a board whose HDMI connector belongs to a device in front of this
receiver, the connector's +5V line goes to that device and hpd-gpios is
absent. tx_5v_power_present() then reads a NULL descriptor, which
gpiod_get_value_cansleep() reports as zero, so the receiver never sees a
source and hdmirx_plugin() never runs.
Have the device in front tell us. Once it is bound through our async
notifier its sd->v4l2_dev is ours, so v4l2_subdev_notify() lands in the
callback installed here, and V4L2_DEVICE_NOTIFY_RX_POWER_PRESENT stands
in for the 5V interrupt this board does not have. Remember what arrives
in source_5v, which tx_5v_power_present() returns when there is no GPIO
of our own, so everything else carries on unchanged and no pointer into
the other device is kept.
A notification is an edge, so it says nothing about a source that was
connected all along. Ask g_input_status() once at bind and take
V4L2_IN_ST_NO_POWER as the answer. A subdev without that op is bound
anyway: a receiver that sees no source beats one that refuses to probe.
Without a det_irq there is nothing to disable_irq() across the cancel in
hdmirx_disable_irq(), so a notification can arm the hotplug work right
afterwards - including from hdmirx_suspend(), on its way to gating the
clocks. Gate both workers on hotplug_on under work_lock instead. It
starts clear, which also keeps them off the hardware during probe, where
hdmirx_fwnode_bound() can arm the work well before the EDID is written.
The flag is not enough on the way out. The worker still has to reach
work_lock to read it, and hdmirx_fwnode_unbind() arms the work once more
from inside v4l2_async_nf_unregister(), after hdmirx_disable_irq() has
already cancelled it. hdmirx_dev is devm allocated, so by the time the
worker runs it can be gone. Cancel the work again once the notifier is
unregistered, in hdmirx_remove() and in the probe error path.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
.../media/platform/synopsys/hdmirx/snps_hdmirx.c | 121 ++++++++++++++++++++-
1 file changed, 118 insertions(+), 3 deletions(-)
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
index 23b4dd853be57..43d7857b4458b 100644
--- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
@@ -142,6 +142,7 @@ struct snps_hdmirx_dev {
struct mutex phy_rw_lock; /* to protect phy r/w configuration */
struct mutex stream_lock; /* to lock video stream capture */
struct mutex work_lock; /* to lock the critical section of hotplug event */
+ bool hotplug_on; /* under work_lock: the hardware is up, workers may run */
struct reset_control_bulk_data resets[HDMIRX_NUM_RST];
struct clk_bulk_data *clks;
struct regmap *grf;
@@ -160,6 +161,7 @@ struct snps_hdmirx_dev {
bool hpd_trigger_level_high;
bool tmds_clk_ratio;
bool plugged;
+ bool source_5v; /* 5V as last reported by the subdev in front of us */
int num_clks;
u32 edid_blocks_written;
u32 cur_fmt_fourcc;
@@ -237,6 +239,15 @@ static bool tx_5v_power_present(struct snps_hdmirx_dev *hdmirx_dev)
int val, i, cnt = 0;
bool ret;
+ /*
+ * Without a GPIO of our own the connector belongs to the subdev in
+ * front of us and there is no line here to sample, so sampling is
+ * reading back what that subdev last told us. Nothing to debounce:
+ * it did that on its side of the connector.
+ */
+ if (!hdmirx_dev->detect_5v_gpio)
+ return READ_ONCE(hdmirx_dev->source_5v);
+
for (i = 0; i < 10; i++) {
usleep_range(1000, 1100);
val = gpiod_get_value_cansleep(hdmirx_dev->detect_5v_gpio);
@@ -2227,6 +2238,11 @@ static void hdmirx_delayed_work_hotplug(struct work_struct *work)
delayed_work_hotplug.work);
mutex_lock(&hdmirx_dev->work_lock);
+ if (!hdmirx_dev->hotplug_on) {
+ mutex_unlock(&hdmirx_dev->work_lock);
+ return;
+ }
+
plugin = tx_5v_power_present(hdmirx_dev);
v4l2_ctrl_s_ctrl(hdmirx_dev->detect_tx_5v_ctrl, plugin);
v4l2_dbg(1, debug, &hdmirx_dev->v4l2_dev, "%s: plugin:%d\n",
@@ -2249,6 +2265,11 @@ static void hdmirx_delayed_work_res_change(struct work_struct *work)
delayed_work_res_change.work);
mutex_lock(&hdmirx_dev->work_lock);
+ if (!hdmirx_dev->hotplug_on) {
+ mutex_unlock(&hdmirx_dev->work_lock);
+ return;
+ }
+
plugin = tx_5v_power_present(hdmirx_dev);
v4l2_dbg(1, debug, &hdmirx_dev->v4l2_dev, "%s: plugin:%d\n",
__func__, plugin);
@@ -2272,17 +2293,39 @@ static void hdmirx_delayed_work_res_change(struct work_struct *work)
mutex_unlock(&hdmirx_dev->work_lock);
}
-static irqreturn_t hdmirx_5v_det_irq_handler(int irq, void *dev_id)
+/*
+ * A 5V edge. Neither source of one says more than "look again": the
+ * hotplug worker samples tx_5v_power_present() and acts on what it finds.
+ */
+static void hdmirx_5v_edge(struct snps_hdmirx_dev *hdmirx_dev)
{
- struct snps_hdmirx_dev *hdmirx_dev = dev_id;
-
queue_delayed_work(system_dfl_wq,
&hdmirx_dev->delayed_work_hotplug,
msecs_to_jiffies(10));
+}
+
+static irqreturn_t hdmirx_5v_det_irq_handler(int irq, void *dev_id)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_id;
+
+ hdmirx_5v_edge(hdmirx_dev);
return IRQ_HANDLED;
}
+/*
+ * The same edge from a subdev in front of us, on a board where the
+ * connector is its and not ours. It has no line here to leave asserted for
+ * the worker to sample, so remember what it saw on its side.
+ */
+static void hdmirx_5v_source_edge(struct snps_hdmirx_dev *hdmirx_dev,
+ bool present)
+{
+ WRITE_ONCE(hdmirx_dev->source_5v, present);
+
+ hdmirx_5v_edge(hdmirx_dev);
+}
+
static const struct hdmirx_cec_ops hdmirx_cec_ops = {
.write = hdmirx_writel,
.read = hdmirx_readl,
@@ -2525,6 +2568,17 @@ static void hdmirx_disable_irq(struct device *dev)
{
struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
+ /*
+ * Disabling det_irq is what stops the work being armed again, but
+ * there is no det_irq when the 5V state comes from a subdev: it can
+ * notify us into hdmirx_5v_source_edge() right after the cancel
+ * below. Have the workers bail out instead. They take work_lock
+ * first thing, so anything queued from here on sees this.
+ */
+ mutex_lock(&hdmirx_dev->work_lock);
+ hdmirx_dev->hotplug_on = false;
+ mutex_unlock(&hdmirx_dev->work_lock);
+
if (hdmirx_dev->det_irq > 0)
disable_irq(hdmirx_dev->det_irq);
disable_irq(hdmirx_dev->dma_irq);
@@ -2538,6 +2592,15 @@ static void hdmirx_enable_irq(struct device *dev)
{
struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
+ /*
+ * Also the first time the workers are let in: hdmirx_fwnode_bound()
+ * can arm the hotplug work from within hdmirx_probe(), well before
+ * the EDID is written and HDCP is registered.
+ */
+ mutex_lock(&hdmirx_dev->work_lock);
+ hdmirx_dev->hotplug_on = true;
+ mutex_unlock(&hdmirx_dev->work_lock);
+
enable_irq(hdmirx_dev->hdmi_irq);
enable_irq(hdmirx_dev->dma_irq);
if (hdmirx_dev->det_irq > 0)
@@ -2673,6 +2736,18 @@ static int hdmirx_register_cec(struct snps_hdmirx_dev *hdmirx_dev,
return 0;
}
+static void hdmirx_notify(struct v4l2_subdev *sd, unsigned int notification,
+ void *arg)
+{
+ struct snps_hdmirx_dev *hdmirx_dev =
+ container_of(sd->v4l2_dev, struct snps_hdmirx_dev, v4l2_dev);
+
+ if (notification != V4L2_DEVICE_NOTIFY_RX_POWER_PRESENT || !arg)
+ return;
+
+ hdmirx_5v_source_edge(hdmirx_dev, !!*(unsigned int *)arg);
+}
+
static int hdmirx_fwnode_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *subdev,
struct v4l2_async_connection *asc)
@@ -2680,6 +2755,7 @@ static int hdmirx_fwnode_bound(struct v4l2_async_notifier *notifier,
struct snps_hdmirx_dev *hdmirx_dev =
container_of(notifier, struct snps_hdmirx_dev, notifier);
int source_pad;
+ u32 status;
int ret;
source_pad = media_entity_get_fwnode_pad(&subdev->entity,
@@ -2699,6 +2775,23 @@ static int hdmirx_fwnode_bound(struct v4l2_async_notifier *notifier,
return ret;
}
+ if (hdmirx_dev->detect_5v_gpio)
+ return 0;
+
+ /*
+ * Changes arrive through hdmirx_notify(). Ask once for the state a
+ * source connected before we bound is already in.
+ */
+ ret = v4l2_subdev_call(subdev, video, g_input_status, &status);
+ if (ret) {
+ dev_err(hdmirx_dev->dev,
+ "%s did not report the input status and there is no hpd-gpios, no source will be detected: %d\n",
+ subdev->name, ret);
+ return 0;
+ }
+
+ hdmirx_5v_source_edge(hdmirx_dev, !(status & V4L2_IN_ST_NO_POWER));
+
return 0;
}
@@ -2710,8 +2803,24 @@ static int hdmirx_fwnode_complete(struct v4l2_async_notifier *notifier)
return v4l2_device_register_subdev_nodes(&hdmirx_dev->v4l2_dev);
}
+static void hdmirx_fwnode_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *asc)
+{
+ struct snps_hdmirx_dev *hdmirx_dev =
+ container_of(notifier, struct snps_hdmirx_dev, notifier);
+
+ /* With a GPIO of our own the connector is ours and stays put. */
+ if (hdmirx_dev->detect_5v_gpio)
+ return;
+
+ /* The source went with it. */
+ hdmirx_5v_source_edge(hdmirx_dev, false);
+}
+
static const struct v4l2_async_notifier_operations hdmirx_async_ops = {
.bound = hdmirx_fwnode_bound,
+ .unbind = hdmirx_fwnode_unbind,
.complete = hdmirx_fwnode_complete,
};
@@ -2806,6 +2915,7 @@ static int hdmirx_probe(struct platform_device *pdev)
goto err_pm;
}
hdmirx_dev->v4l2_dev.ctrl_handler = hdl;
+ hdmirx_dev->v4l2_dev.notify = hdmirx_notify;
ret = v4l2_device_register(dev, &hdmirx_dev->v4l2_dev);
if (ret < 0) {
@@ -2890,6 +3000,8 @@ static int hdmirx_probe(struct platform_device *pdev)
v4l2_async_nf_unregister(&hdmirx_dev->notifier);
err_cleanup_notifier:
v4l2_async_nf_cleanup(&hdmirx_dev->notifier);
+ /* Binding and unbinding both arm the hotplug work. */
+ cancel_delayed_work_sync(&hdmirx_dev->delayed_work_hotplug);
err_unreg_media:
media_device_unregister(&hdmirx_dev->mdev);
err_unreg_video_dev:
@@ -2926,6 +3038,9 @@ static void hdmirx_remove(struct platform_device *pdev)
v4l2_async_nf_unregister(&hdmirx_dev->notifier);
v4l2_async_nf_cleanup(&hdmirx_dev->notifier);
+ /* The unbind above arms the hotplug work again. */
+ cancel_delayed_work_sync(&hdmirx_dev->delayed_work_hotplug);
+
media_device_unregister(&hdmirx_dev->mdev);
vb2_video_unregister_device(&hdmirx_dev->stream.vdev);
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread