[PATCH v2 30/66] media: sun6i-csi: Add bridge v4l2 subdev with port management

Sakari Ailus sakari.ailus at linux.intel.com
Mon Feb 14 10:12:57 PST 2022


Hi Paul,

On Sat, Feb 05, 2022 at 07:53:53PM +0100, Paul Kocialkowski wrote:
> Introduce a bridge v4l2 subdev to prepare for separation between the
> processing part (bridge) and the dma engine, which is required to
> properly support ths isp workflow later on.
> 
> Currently the bridge just manages fwnode mapping to media pads,
> using an async notifier (which was previously in the main code).
> The s_stream video op just forwards to the connected v4l2 subdev
> (sensor or MIPI CSI-2 bridge).
> 
> The video capture device is now registered after the bridge and
> attaches to it with a media link.
> 
> Signed-off-by: Paul Kocialkowski <paul.kocialkowski at bootlin.com>
> ---
>  .../media/platform/sunxi/sun6i-csi/Makefile   |   2 +-
>  .../platform/sunxi/sun6i-csi/sun6i_csi.c      | 156 +-----
>  .../platform/sunxi/sun6i-csi/sun6i_csi.h      |  12 +-
>  .../sunxi/sun6i-csi/sun6i_csi_bridge.c        | 473 ++++++++++++++++++
>  .../sunxi/sun6i-csi/sun6i_csi_bridge.h        |  44 ++
>  .../platform/sunxi/sun6i-csi/sun6i_video.c    |  19 +
>  6 files changed, 571 insertions(+), 135 deletions(-)
>  create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c
>  create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h
> 
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile
> index e7e315347804..7a699580a641 100644
> --- a/drivers/media/platform/sunxi/sun6i-csi/Makefile
> +++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile
> @@ -1,4 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> -sun6i-csi-y += sun6i_video.o sun6i_csi.o
> +sun6i-csi-y += sun6i_video.o sun6i_csi.o sun6i_csi_bridge.o
>  
>  obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
> index c8fe31cc38b5..a1847ae3e88e 100644
> --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
> @@ -34,16 +34,17 @@
>  bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev,
>  				   u32 pixformat, u32 mbus_code)
>  {
> -	struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
> +	struct v4l2_fwnode_endpoint *endpoint =
> +		&csi_dev->bridge.source->endpoint;
>  
>  	/*
>  	 * Some video receivers have the ability to be compatible with
>  	 * 8bit and 16bit bus width.
>  	 * Identify the media bus format from device tree.
>  	 */
> -	if ((v4l2->v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
> -	     || v4l2->v4l2_ep.bus_type == V4L2_MBUS_BT656)
> -	     && v4l2->v4l2_ep.bus.parallel.bus_width == 16) {
> +	if ((endpoint->bus_type == V4L2_MBUS_PARALLEL
> +	     || endpoint->bus_type == V4L2_MBUS_BT656)
> +	     && endpoint->bus.parallel.bus_width == 16) {
>  		switch (pixformat) {
>  		case V4L2_PIX_FMT_NV12_16L16:
>  		case V4L2_PIX_FMT_NV12:
> @@ -328,7 +329,8 @@ static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_device *csi_dev,
>  
>  static void sun6i_csi_setup_bus(struct sun6i_csi_device *csi_dev)
>  {
> -	struct v4l2_fwnode_endpoint *endpoint = &csi_dev->v4l2.v4l2_ep;
> +	struct v4l2_fwnode_endpoint *endpoint =
> +		&csi_dev->bridge.source->endpoint;
>  	struct sun6i_csi_config *config = &csi_dev->config;
>  	unsigned char bus_width;
>  	u32 flags;
> @@ -583,95 +585,11 @@ static const struct media_device_ops sun6i_csi_media_ops = {
>  
>  /* V4L2 */
>  
> -static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev,
> -				 struct media_entity *entity,
> -				 struct fwnode_handle *fwnode)
> +int sun6i_csi_v4l2_complete(struct sun6i_csi_device *csi_dev)
>  {
> -	struct media_entity *sink;
> -	struct media_pad *sink_pad;
> -	int src_pad_index;
> -	int ret;
> -
> -	ret = media_entity_get_fwnode_pad(entity, fwnode, MEDIA_PAD_FL_SOURCE);
> -	if (ret < 0) {
> -		dev_err(csi_dev->dev,
> -			"%s: no source pad in external entity %s\n", __func__,
> -			entity->name);
> -		return -EINVAL;
> -	}
> -
> -	src_pad_index = ret;
> -
> -	sink = &csi_dev->video.video_dev.entity;
> -	sink_pad = &csi_dev->video.pad;
> -
> -	dev_dbg(csi_dev->dev, "creating %s:%u -> %s:%u link\n",
> -		entity->name, src_pad_index, sink->name, sink_pad->index);
> -	ret = media_create_pad_link(entity, src_pad_index, sink,
> -				    sink_pad->index,
> -				    MEDIA_LNK_FL_ENABLED |
> -				    MEDIA_LNK_FL_IMMUTABLE);
> -	if (ret < 0) {
> -		dev_err(csi_dev->dev, "failed to create %s:%u -> %s:%u link\n",
> -			entity->name, src_pad_index,
> -			sink->name, sink_pad->index);
> -		return ret;
> -	}
> -
> -	return 0;
> -}
> -
> -static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier)
> -{
> -	struct sun6i_csi_device *csi_dev =
> -		container_of(notifier, struct sun6i_csi_device,
> -			     v4l2.notifier);
> -	struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
> -	struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev;
> -	struct v4l2_subdev *sd;
> -	int ret;
> -
> -	dev_dbg(csi_dev->dev, "notify complete, all subdevs registered\n");
> -
> -	sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list);
> -	if (!sd)
> -		return -EINVAL;
> -
> -	ret = sun6i_csi_link_entity(csi_dev, &sd->entity, sd->fwnode);
> -	if (ret < 0)
> -		return ret;
> -
> -	ret = v4l2_device_register_subdev_nodes(v4l2_dev);
> -	if (ret < 0)
> -		return ret;
> -
> -	return 0;
> -}
> -
> -static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = {
> -	.complete = sun6i_subdev_notify_complete,
> -};
> -
> -static int sun6i_csi_fwnode_parse(struct device *dev,
> -				  struct v4l2_fwnode_endpoint *vep,
> -				  struct v4l2_async_subdev *asd)
> -{
> -	struct sun6i_csi_device *csi_dev = dev_get_drvdata(dev);
> +	struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev;
>  
> -	if (vep->base.port || vep->base.id) {
> -		dev_warn(dev, "Only support a single port with one endpoint\n");
> -		return -ENOTCONN;
> -	}
> -
> -	switch (vep->bus_type) {
> -	case V4L2_MBUS_PARALLEL:
> -	case V4L2_MBUS_BT656:
> -		csi_dev->v4l2.v4l2_ep = *vep;
> -		return 0;
> -	default:
> -		dev_err(dev, "Unsupported media bus type\n");
> -		return -ENOTCONN;
> -	}
> +	return v4l2_device_register_subdev_nodes(v4l2_dev);
>  }
>  
>  static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev)
> @@ -679,7 +597,6 @@ static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev)
>  	struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
>  	struct media_device *media_dev = &v4l2->media_dev;
>  	struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev;
> -	struct v4l2_async_notifier *notifier = &v4l2->notifier;
>  	struct device *dev = csi_dev->dev;
>  	int ret;
>  
> @@ -720,42 +637,8 @@ static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev)
>  		goto error_v4l2_ctrl;
>  	}
>  
> -	/* Video */
> -
> -	ret = sun6i_video_setup(csi_dev);
> -	if (ret)
> -		goto error_v4l2_device;
> -
> -	/* V4L2 Async */
> -
> -	v4l2_async_nf_init(notifier);
> -	notifier->ops = &sun6i_csi_async_ops;
> -
> -	ret = v4l2_async_nf_parse_fwnode_endpoints(dev, notifier,
> -						   sizeof(struct
> -							  v4l2_async_subdev),
> -						   sun6i_csi_fwnode_parse);
> -	if (ret)
> -		goto error_video;
> -
> -	ret = v4l2_async_nf_register(v4l2_dev, notifier);
> -	if (ret) {
> -		dev_err(dev, "failed to register v4l2 async notifier: %d\n",
> -			ret);
> -		goto error_v4l2_async_notifier;
> -	}
> -
>  	return 0;
>  
> -error_v4l2_async_notifier:
> -	v4l2_async_nf_cleanup(notifier);
> -
> -error_video:
> -	sun6i_video_cleanup(csi_dev);
> -
> -error_v4l2_device:
> -	v4l2_device_unregister(&v4l2->v4l2_dev);
> -
>  error_v4l2_ctrl:
>  	v4l2_ctrl_handler_free(&v4l2->ctrl_handler);
>  
> @@ -771,9 +654,6 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev)
>  	struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
>  
>  	media_device_unregister(&v4l2->media_dev);
> -	v4l2_async_nf_unregister(&v4l2->notifier);
> -	v4l2_async_nf_cleanup(&v4l2->notifier);
> -	sun6i_video_cleanup(csi_dev);
>  	v4l2_device_unregister(&v4l2->v4l2_dev);
>  	v4l2_ctrl_handler_free(&v4l2->ctrl_handler);
>  	media_device_cleanup(&v4l2->media_dev);
> @@ -995,8 +875,22 @@ static int sun6i_csi_probe(struct platform_device *platform_dev)
>  	if (ret)
>  		goto error_resources;
>  
> +	ret = sun6i_csi_bridge_setup(csi_dev);
> +	if (ret)
> +		goto error_v4l2;
> +
> +	ret = sun6i_video_setup(csi_dev);
> +	if (ret)
> +		goto error_bridge;
> +
>  	return 0;
>  
> +error_bridge:
> +	sun6i_csi_bridge_cleanup(csi_dev);
> +
> +error_v4l2:
> +	sun6i_csi_v4l2_cleanup(csi_dev);
> +
>  error_resources:
>  	sun6i_csi_resources_cleanup(csi_dev);
>  
> @@ -1007,6 +901,8 @@ static int sun6i_csi_remove(struct platform_device *pdev)
>  {
>  	struct sun6i_csi_device *csi_dev = platform_get_drvdata(pdev);
>  
> +	sun6i_video_cleanup(csi_dev);
> +	sun6i_csi_bridge_cleanup(csi_dev);
>  	sun6i_csi_v4l2_cleanup(csi_dev);
>  	sun6i_csi_resources_cleanup(csi_dev);
>  
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
> index 4dd83e57bafa..576c7f10289e 100644
> --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
> @@ -13,11 +13,16 @@
>  #include <media/v4l2-fwnode.h>
>  #include <media/videobuf2-v4l2.h>
>  
> +#include "sun6i_csi_bridge.h"
>  #include "sun6i_video.h"
>  
>  #define SUN6I_CSI_NAME		"sun6i-csi"
>  #define SUN6I_CSI_DESCRIPTION	"Allwinner A31 CSI Device"
>  
> +enum sun6i_csi_port {
> +	SUN6I_CSI_PORT_PARALLEL		= 0,
> +};
> +
>  struct sun6i_csi_buffer {
>  	struct vb2_v4l2_buffer		v4l2_buffer;
>  	struct list_head		list;
> @@ -46,10 +51,6 @@ struct sun6i_csi_v4l2 {
>  	struct v4l2_device		v4l2_dev;
>  	struct v4l2_ctrl_handler	ctrl_handler;
>  	struct media_device		media_dev;
> -
> -	struct v4l2_async_notifier	notifier;
> -	/* video port settings */
> -	struct v4l2_fwnode_endpoint	v4l2_ep;
>  };
>  
>  struct sun6i_csi_device {
> @@ -57,6 +58,7 @@ struct sun6i_csi_device {
>  
>  	struct sun6i_csi_config		config;
>  	struct sun6i_csi_v4l2		v4l2;
> +	struct sun6i_csi_bridge		bridge;
>  	struct sun6i_video		video;
>  
>  	struct regmap			*regmap;
> @@ -156,4 +158,6 @@ static inline int sun6i_csi_get_bpp(unsigned int pixformat)
>  	return 0;
>  }
>  
> +int sun6i_csi_v4l2_complete(struct sun6i_csi_device *csi_dev);
> +
>  #endif /* __SUN6I_CSI_H__ */
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c
> new file mode 100644
> index 000000000000..74706d883359
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c
> @@ -0,0 +1,473 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski at bootlin.com>
> + */
> +
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#include "sun6i_csi.h"
> +#include "sun6i_csi_bridge.h"
> +
> +/* Format */
> +
> +static const u32 sun6i_csi_bridge_mbus_codes[] = {
> +	/* Bayer */
> +	MEDIA_BUS_FMT_SBGGR8_1X8,
> +	MEDIA_BUS_FMT_SGBRG8_1X8,
> +	MEDIA_BUS_FMT_SGRBG8_1X8,
> +	MEDIA_BUS_FMT_SRGGB8_1X8,
> +	MEDIA_BUS_FMT_SBGGR10_1X10,
> +	MEDIA_BUS_FMT_SGBRG10_1X10,
> +	MEDIA_BUS_FMT_SGRBG10_1X10,
> +	MEDIA_BUS_FMT_SRGGB10_1X10,
> +	MEDIA_BUS_FMT_SBGGR12_1X12,
> +	MEDIA_BUS_FMT_SGBRG12_1X12,
> +	MEDIA_BUS_FMT_SGRBG12_1X12,
> +	MEDIA_BUS_FMT_SRGGB12_1X12,
> +	/* RGB */
> +	MEDIA_BUS_FMT_RGB565_2X8_LE,
> +	MEDIA_BUS_FMT_RGB565_2X8_BE,
> +	/* YUV422 */
> +	MEDIA_BUS_FMT_YUYV8_2X8,
> +	MEDIA_BUS_FMT_UYVY8_2X8,
> +	MEDIA_BUS_FMT_YVYU8_2X8,
> +	MEDIA_BUS_FMT_UYVY8_2X8,
> +	MEDIA_BUS_FMT_VYUY8_2X8,
> +	MEDIA_BUS_FMT_YUYV8_1X16,
> +	MEDIA_BUS_FMT_UYVY8_1X16,
> +	MEDIA_BUS_FMT_YVYU8_1X16,
> +	MEDIA_BUS_FMT_UYVY8_1X16,
> +	MEDIA_BUS_FMT_VYUY8_1X16,
> +	/* Compressed */
> +	MEDIA_BUS_FMT_JPEG_1X8,
> +};
> +
> +static bool sun6i_csi_bridge_mbus_code_check(u32 mbus_code)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(sun6i_csi_bridge_mbus_codes); i++)
> +		if (sun6i_csi_bridge_mbus_codes[i] == mbus_code)
> +			return true;
> +
> +	return false;
> +}
> +
> +/* V4L2 Subdev */
> +
> +static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on)
> +{
> +	struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);
> +	struct v4l2_subdev *source_subdev;
> +	/* Initialize to 0 to use both in disable label (ret != 0) and off. */
> +	int ret = 0;
> +
> +	/* Source */
> +
> +	if (!csi_dev->bridge.source)
> +		return -ENODEV;
> +
> +	source_subdev = csi_dev->bridge.source->subdev;
> +
> +	if (!on) {
> +		v4l2_subdev_call(source_subdev, video, s_stream, 0);
> +		goto disable;
> +	}
> +
> +	ret = v4l2_subdev_call(source_subdev, video, s_stream, 1);
> +	if (ret && ret != -ENOIOCTLCMD)
> +		goto disable;
> +
> +	return 0;
> +
> +disable:
> +	csi_dev->bridge.source = NULL;
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_video_ops sun6i_csi_bridge_video_ops = {
> +	.s_stream	= sun6i_csi_bridge_s_stream,
> +};
> +
> +static void
> +sun6i_csi_bridge_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format)
> +{
> +	if (!sun6i_csi_bridge_mbus_code_check(mbus_format->code))
> +		mbus_format->code = sun6i_csi_bridge_mbus_codes[0];
> +
> +	mbus_format->field = V4L2_FIELD_NONE;
> +	mbus_format->colorspace = V4L2_COLORSPACE_RAW;
> +	mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT;
> +	mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +}
> +
> +static int sun6i_csi_bridge_init_cfg(struct v4l2_subdev *subdev,
> +				     struct v4l2_subdev_state *state)
> +{
> +	unsigned int pad = SUN6I_CSI_BRIDGE_PAD_SINK;
> +	struct v4l2_mbus_framefmt *mbus_format =
> +		v4l2_subdev_get_try_format(subdev, state, pad);
> +
> +	mbus_format->code = sun6i_csi_bridge_mbus_codes[0];
> +	mbus_format->width = 1280;
> +	mbus_format->height = 720;
> +
> +	sun6i_csi_bridge_mbus_format_prepare(mbus_format);
> +
> +	return 0;
> +}
> +
> +static int
> +sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev,
> +				struct v4l2_subdev_state *state,
> +				struct v4l2_subdev_mbus_code_enum *code_enum)
> +{
> +	if (code_enum->index >= ARRAY_SIZE(sun6i_csi_bridge_mbus_codes))
> +		return -EINVAL;
> +
> +	code_enum->code = sun6i_csi_bridge_mbus_codes[code_enum->index];
> +
> +	return 0;
> +}
> +
> +static int sun6i_csi_bridge_get_fmt(struct v4l2_subdev *subdev,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_format *format)
> +{
> +	struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);
> +	struct v4l2_mbus_framefmt *mbus_format = &format->format;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> +		*mbus_format = *v4l2_subdev_get_try_format(subdev, state,
> +							   format->pad);
> +	else
> +		*mbus_format = csi_dev->bridge.mbus_format;
> +
> +	return 0;
> +}
> +
> +static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_format *format)
> +{
> +	struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);
> +	struct v4l2_mbus_framefmt *mbus_format = &format->format;
> +
> +	sun6i_csi_bridge_mbus_format_prepare(mbus_format);
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> +		*v4l2_subdev_get_try_format(subdev, state, format->pad) =
> +			*mbus_format;
> +	else
> +		csi_dev->bridge.mbus_format = *mbus_format;

Note that the driver is responsible for serialising access to its data,
i.e. you have to acquire the mutex here.

> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops sun6i_csi_bridge_pad_ops = {
> +	.init_cfg	= sun6i_csi_bridge_init_cfg,
> +	.enum_mbus_code	= sun6i_csi_bridge_enum_mbus_code,
> +	.get_fmt	= sun6i_csi_bridge_get_fmt,
> +	.set_fmt	= sun6i_csi_bridge_set_fmt,
> +};
> +
> +const struct v4l2_subdev_ops sun6i_csi_bridge_subdev_ops = {
> +	.video	= &sun6i_csi_bridge_video_ops,
> +	.pad	= &sun6i_csi_bridge_pad_ops,
> +};
> +
> +/* Media Entity */
> +
> +static int sun6i_csi_bridge_link_validate(struct media_link *link)
> +{
> +	struct v4l2_subdev *subdev =
> +		media_entity_to_v4l2_subdev(link->sink->entity);
> +	struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);
> +	struct sun6i_csi_bridge *bridge = &csi_dev->bridge;
> +	struct device *dev = csi_dev->dev;
> +	struct v4l2_subdev *source_subdev =
> +		media_entity_to_v4l2_subdev(link->source->entity);
> +	int ret;
> +
> +	/* Only support one enabled source at a time. */
> +	if (bridge->source) {
> +		dev_err(dev, "multiple sources are connected to the bridge\n");
> +		return -EBUSY;
> +	}
> +
> +	ret = v4l2_subdev_link_validate(link);
> +	if (ret)
> +		return ret;
> +
> +	if (source_subdev == bridge->source_parallel.subdev)
> +		bridge->source = &bridge->source_parallel;
> +	else

Useless use of else.

> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static const struct media_entity_operations sun6i_csi_bridge_entity_ops = {
> +	.link_validate	= sun6i_csi_bridge_link_validate,
> +};
> +
> +/* V4L2 Async */
> +
> +static int sun6i_csi_bridge_link(struct sun6i_csi_device *csi_dev,
> +				 int sink_pad_index,
> +				 struct v4l2_subdev *remote_subdev,
> +				 bool enabled)
> +{
> +	struct device *dev = csi_dev->dev;
> +	struct v4l2_subdev *subdev = &csi_dev->bridge.subdev;
> +	struct media_entity *sink_entity = &subdev->entity;
> +	struct media_entity *source_entity = &remote_subdev->entity;
> +	int source_pad_index;
> +	int ret;
> +
> +	/* Get the first remote source pad. */
> +	ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode,
> +					  MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(dev, "missing source pad in external entity %s\n",
> +			source_entity->name);
> +		return -EINVAL;
> +	}
> +
> +	source_pad_index = ret;
> +
> +	dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name,
> +		source_pad_index, sink_entity->name, sink_pad_index);
> +
> +	ret = media_create_pad_link(source_entity, source_pad_index,
> +				    sink_entity, sink_pad_index,
> +				    enabled ? MEDIA_LNK_FL_ENABLED : 0);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to create %s:%u -> %s:%u link\n",
> +			source_entity->name, source_pad_index,
> +			sink_entity->name, sink_pad_index);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +sun6i_csi_bridge_notifier_bound(struct v4l2_async_notifier *notifier,
> +				struct v4l2_subdev *remote_subdev,
> +				struct v4l2_async_subdev *async_subdev)
> +{
> +	struct sun6i_csi_device *csi_dev =
> +		container_of(notifier, struct sun6i_csi_device,
> +			     bridge.notifier);
> +	struct sun6i_csi_bridge *bridge = &csi_dev->bridge;
> +	struct sun6i_csi_bridge_source *source = NULL;
> +	struct fwnode_handle *fwnode = dev_fwnode(csi_dev->dev);
> +	struct fwnode_handle *handle = NULL;
> +	bool enabled;
> +	int ret;
> +
> +	while ((handle = fwnode_graph_get_next_endpoint(fwnode, handle))) {

I'd instead store the information you need here in struct sun6i_csi_bridge.
You could remove the loop here.

> +		struct fwnode_endpoint endpoint = { 0 };
> +		struct fwnode_handle *remote_fwnode;
> +
> +		remote_fwnode = fwnode_graph_get_remote_port_parent(handle);
> +		if (!remote_fwnode)
> +			continue;
> +
> +		if (remote_fwnode != remote_subdev->fwnode)
> +			goto next;
> +
> +		ret = fwnode_graph_parse_endpoint(handle, &endpoint);
> +		if (ret < 0)
> +			goto next;
> +
> +		switch (endpoint.port) {
> +		case SUN6I_CSI_PORT_PARALLEL:
> +			source = &bridge->source_parallel;
> +			enabled = true;
> +			break;
> +		default:
> +			break;
> +		}
> +
> +next:
> +		fwnode_handle_put(remote_fwnode);
> +	}
> +
> +	if (!source)
> +		return -EINVAL;
> +
> +	source->subdev = remote_subdev;
> +
> +	return sun6i_csi_bridge_link(csi_dev, SUN6I_CSI_BRIDGE_PAD_SINK,
> +				     remote_subdev, enabled);
> +}
> +
> +static int
> +sun6i_csi_bridge_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct sun6i_csi_device *csi_dev =
> +		container_of(notifier, struct sun6i_csi_device,
> +			     bridge.notifier);
> +
> +	return sun6i_csi_v4l2_complete(csi_dev);

You could call v4l2_device_register_subdev_nodes() here.

> +}
> +
> +static const struct v4l2_async_notifier_operations
> +sun6i_csi_bridge_notifier_ops = {
> +	.bound		= sun6i_csi_bridge_notifier_bound,
> +	.complete	= sun6i_csi_bridge_notifier_complete,
> +};
> +
> +/* Bridge */
> +
> +static int sun6i_csi_bridge_source_setup(struct sun6i_csi_device *csi_dev,
> +					 struct sun6i_csi_bridge_source *source,
> +					 u32 port,
> +					 enum v4l2_mbus_type *bus_types)
> +{
> +	struct device *dev = csi_dev->dev;
> +	struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier;
> +	struct v4l2_fwnode_endpoint *endpoint = &source->endpoint;
> +	struct v4l2_async_subdev *async_subdev;
> +	struct fwnode_handle *handle;
> +	int ret;
> +
> +	handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), port, 0, 0);
> +	if (!handle)
> +		return -ENODEV;
> +
> +	ret = v4l2_fwnode_endpoint_parse(handle, endpoint);
> +	if (ret)
> +		goto complete;
> +
> +	if (bus_types) {
> +		bool valid = false;
> +		unsigned int i;
> +
> +		for (i = 0; bus_types[i] != V4L2_MBUS_INVALID; i++) {
> +			if (endpoint->bus_type == bus_types[i]) {
> +				valid = true;
> +				break;
> +			}
> +		}
> +
> +		if (!valid) {
> +			dev_err(dev, "unsupported bus type for port %d\n",
> +				port);
> +			ret = -EINVAL;
> +			goto complete;
> +		}
> +	}
> +
> +	async_subdev = v4l2_async_nf_add_fwnode_remote(notifier, handle,
> +		struct v4l2_async_subdev);
> +	if (IS_ERR(async_subdev)) {
> +		ret = PTR_ERR(async_subdev);
> +		goto complete;
> +	}
> +
> +	source->expected = true;
> +
> +complete:
> +	fwnode_handle_put(handle);
> +
> +	return ret;
> +}
> +
> +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev)
> +{
> +	struct device *dev = csi_dev->dev;
> +	struct sun6i_csi_bridge *bridge = &csi_dev->bridge;
> +	struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev;
> +	struct v4l2_subdev *subdev = &bridge->subdev;
> +	struct v4l2_async_notifier *notifier = &bridge->notifier;
> +	struct media_pad *pads = bridge->pads;
> +	enum v4l2_mbus_type parallel_mbus_types[] = {
> +		V4L2_MBUS_PARALLEL,
> +		V4L2_MBUS_BT656,
> +		V4L2_MBUS_INVALID
> +	};
> +	int ret;
> +
> +	/* V4L2 Subdev */
> +
> +	v4l2_subdev_init(subdev, &sun6i_csi_bridge_subdev_ops);
> +	strscpy(subdev->name, SUN6I_CSI_BRIDGE_NAME, sizeof(subdev->name));
> +	subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	subdev->owner = THIS_MODULE;
> +	subdev->dev = dev;
> +
> +	v4l2_set_subdevdata(subdev, csi_dev);
> +
> +	/* Media Entity */
> +
> +	subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	subdev->entity.ops = &sun6i_csi_bridge_entity_ops;
> +
> +	/* Media Pads */
> +
> +	pads[SUN6I_CSI_BRIDGE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	pads[SUN6I_CSI_BRIDGE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE |
> +						  MEDIA_PAD_FL_MUST_CONNECT;
> +
> +	ret = media_entity_pads_init(&subdev->entity,
> +				     SUN6I_CSI_BRIDGE_PAD_COUNT, pads);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* V4L2 Subdev */
> +
> +	ret = v4l2_device_register_subdev(v4l2_dev, subdev);
> +	if (ret) {
> +		dev_err(dev, "failed to register v4l2 subdev: %d\n", ret);
> +		goto error_media_entity;
> +	}
> +
> +	/* V4L2 Async */
> +
> +	v4l2_async_nf_init(notifier);
> +	notifier->ops = &sun6i_csi_bridge_notifier_ops;
> +
> +	sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_parallel,
> +				      SUN6I_CSI_PORT_PARALLEL,
> +				      parallel_mbus_types);
> +
> +	ret = v4l2_async_nf_register(v4l2_dev, notifier);
> +	if (ret) {
> +		dev_err(dev, "failed to register v4l2 async notifier: %d\n",
> +			ret);
> +		goto error_v4l2_async_notifier;
> +	}
> +
> +	return 0;
> +
> +error_v4l2_async_notifier:
> +	v4l2_async_nf_cleanup(notifier);
> +
> +	v4l2_device_unregister_subdev(subdev);
> +
> +error_media_entity:
> +	media_entity_cleanup(&subdev->entity);
> +
> +	return ret;
> +}
> +
> +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev)
> +{
> +	struct v4l2_subdev *subdev = &csi_dev->bridge.subdev;
> +	struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier;
> +
> +	v4l2_async_nf_unregister(notifier);
> +	v4l2_async_nf_cleanup(notifier);
> +
> +	v4l2_device_unregister_subdev(subdev);
> +
> +	media_entity_cleanup(&subdev->entity);
> +}
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h
> new file mode 100644
> index 000000000000..2ee7878102b6
> --- /dev/null
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2021 Bootlin

2022?

> + * Author: Paul Kocialkowski <paul.kocialkowski at bootlin.com>
> + */
> +
> +#ifndef _SUN6I_CSI_BRIDGE_H_
> +#define _SUN6I_CSI_BRIDGE_H_
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#define SUN6I_CSI_BRIDGE_NAME	"sun6i-csi-bridge"
> +
> +enum sun6i_csi_bridge_pad {
> +	SUN6I_CSI_BRIDGE_PAD_SINK	= 0,
> +	SUN6I_CSI_BRIDGE_PAD_SOURCE	= 1,
> +	SUN6I_CSI_BRIDGE_PAD_COUNT	= 2,
> +};
> +
> +struct sun6i_csi_device;
> +
> +struct sun6i_csi_bridge_source {
> +	struct v4l2_subdev		*subdev;
> +	struct v4l2_fwnode_endpoint	endpoint;
> +	bool				expected;
> +};
> +
> +struct sun6i_csi_bridge {
> +	struct v4l2_subdev		subdev;
> +	struct v4l2_async_notifier	notifier;
> +	struct media_pad		pads[2];
> +	struct v4l2_mbus_framefmt	mbus_format;
> +
> +	struct sun6i_csi_bridge_source	source_parallel;
> +	struct sun6i_csi_bridge_source	*source;
> +};
> +
> +/* Bridge */
> +
> +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev);
> +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev);
> +
> +#endif
> diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
> index d32ff6b81f8a..fa5bf3697ace 100644
> --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
> +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
> @@ -632,6 +632,7 @@ int sun6i_video_setup(struct sun6i_csi_device *csi_dev)
>  {
>  	struct sun6i_video *video = &csi_dev->video;
>  	struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev;
> +	struct v4l2_subdev *bridge_subdev = &csi_dev->bridge.subdev;
>  	struct video_device *video_dev = &video->video_dev;
>  	struct vb2_queue *queue = &video->queue;
>  	struct media_pad *pad = &video->pad;
> @@ -715,8 +716,26 @@ int sun6i_video_setup(struct sun6i_csi_device *csi_dev)
>  	v4l2_info(v4l2_dev, "device %s registered as %s\n", video_dev->name,
>  		  video_device_node_name(video_dev));
>  
> +	/* Media Pad Link */
> +
> +	ret = media_create_pad_link(&bridge_subdev->entity,
> +				    SUN6I_CSI_BRIDGE_PAD_SOURCE,
> +				    &video_dev->entity, 0,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret < 0) {
> +		v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n",
> +			 bridge_subdev->entity.name,
> +			 SUN6I_CSI_BRIDGE_PAD_SOURCE,
> +			 video_dev->entity.name, 0);
> +		goto error_video_device;
> +	}
> +
>  	return 0;
>  
> +error_video_device:
> +	vb2_video_unregister_device(video_dev);
> +
>  error_media_entity:
>  	media_entity_cleanup(&video_dev->entity);
>  

-- 
Sakari Ailus



More information about the linux-arm-kernel mailing list