From c201ae84a0ddf1d06e5dddf524e0100aae3c7f3c Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megi@xff.cz>
Date: Sat, 29 Oct 2022 00:01:20 +0200
Subject: [PATCH 034/389] media: sun6i-csi: Add support for multiple endpoints

Devices like tablets and phones can have multiple sensors attached
to the parallel camera interface bus. Add support for switching
between multiple cameras.

Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
 .../platform/sunxi/sun6i-csi/sun6i_csi.c      | 54 ++++++++++---------
 .../platform/sunxi/sun6i-csi/sun6i_csi.h      | 19 +++++--
 .../platform/sunxi/sun6i-csi/sun6i_video.c    | 36 ++++++++++---
 3 files changed, 73 insertions(+), 36 deletions(-)

diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
index 8bf5049dde91..66d267168c82 100644
--- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
@@ -32,18 +32,17 @@
 
 /* TODO add 10&12 bit YUV, RGB support */
 bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev,
-				   u32 pixformat, u32 mbus_code)
+				   u32 pixformat, u32 mbus_code,
+				   struct v4l2_fwnode_endpoint *vep)
 {
-	struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
-
 	/*
 	 * 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 ((vep->bus_type == V4L2_MBUS_PARALLEL
+	     || vep->bus_type == V4L2_MBUS_BT656)
+	     && vep->bus.parallel.bus_width == 16) {
 		switch (pixformat) {
 		case V4L2_PIX_FMT_NV12_16L16:
 		case V4L2_PIX_FMT_NV12:
@@ -330,9 +329,9 @@ static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_device *csi_dev,
 	return CSI_INPUT_SEQ_YUYV;
 }
 
-static void sun6i_csi_setup_bus(struct sun6i_csi_device *csi_dev)
+static void sun6i_csi_setup_bus(struct sun6i_csi_device *csi_dev,
+				struct v4l2_fwnode_endpoint *endpoint)
 {
-	struct v4l2_fwnode_endpoint *endpoint = &csi_dev->v4l2.v4l2_ep;
 	struct sun6i_csi_config *config = &csi_dev->config;
 	unsigned char bus_width;
 	u32 flags;
@@ -530,14 +529,15 @@ static void sun6i_csi_set_window(struct sun6i_csi_device *csi_dev)
 }
 
 int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev,
-			    struct sun6i_csi_config *config)
+			    struct sun6i_csi_config *config,
+			    struct v4l2_fwnode_endpoint *vep)
 {
 	if (!config)
 		return -EINVAL;
 
 	memcpy(&csi_dev->config, config, sizeof(csi_dev->config));
 
-	sun6i_csi_setup_bus(csi_dev);
+	sun6i_csi_setup_bus(csi_dev, vep);
 	sun6i_csi_set_format(csi_dev);
 	sun6i_csi_set_window(csi_dev);
 
@@ -590,7 +590,8 @@ static const struct media_device_ops sun6i_csi_media_ops = {
 
 static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev,
 				 struct media_entity *entity,
-				 struct fwnode_handle *fwnode)
+				 struct fwnode_handle *fwnode,
+				 u32 link_flags)
 {
 	struct media_entity *sink;
 	struct media_pad *sink_pad;
@@ -613,9 +614,7 @@ static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev,
 	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);
+				    sink_pad->index, link_flags);
 	if (ret < 0) {
 		dev_err(csi_dev->dev, "failed to create %s:%u -> %s:%u link\n",
 			entity->name, src_pad_index,
@@ -633,18 +632,24 @@ static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier)
 			     v4l2.notifier);
 	struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
 	struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev;
+	u32 link_flags = MEDIA_LNK_FL_ENABLED;
 	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)
+	if (list_empty(&v4l2_dev->subdevs))
 		return -EINVAL;
 
-	ret = sun6i_csi_link_entity(csi_dev, &sd->entity, sd->fwnode);
-	if (ret < 0)
-		return ret;
+	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
+		ret = sun6i_csi_link_entity(csi_dev, &sd->entity,
+					    sd->fwnode, link_flags);
+		if (ret < 0)
+			return ret;
+
+		/* only enable the first link */
+		link_flags = 0;
+	}
 
 	ret = v4l2_device_register_subdev_nodes(v4l2_dev);
 	if (ret < 0)
@@ -661,17 +666,18 @@ 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 sun6i_csi_async_subdev *casd =
+		container_of(asd, struct sun6i_csi_async_subdev, asd);
 
-	if (vep->base.port || vep->base.id) {
-		dev_warn(dev, "Only support a single port with one endpoint\n");
+	if (vep->base.port) {
+		dev_err(dev, "Only remote entities with a single port are supported\n");
 		return -ENOTCONN;
 	}
 
 	switch (vep->bus_type) {
 	case V4L2_MBUS_PARALLEL:
 	case V4L2_MBUS_BT656:
-		csi_dev->v4l2.v4l2_ep = *vep;
+		casd->vep = *vep;
 		return 0;
 	default:
 		dev_err(dev, "Unsupported media bus type\n");
@@ -727,7 +733,7 @@ static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev)
 
 	ret = v4l2_async_nf_parse_fwnode_endpoints(dev, notifier,
 						   sizeof(struct
-							  v4l2_async_subdev),
+							  sun6i_csi_async_subdev),
 						   sun6i_csi_fwnode_parse);
 	if (ret)
 		goto error_video;
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
index 5559c1b7d6f0..4629f5c29356 100644
--- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
@@ -10,6 +10,7 @@
 
 #include <media/v4l2-device.h>
 #include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
 #include <media/videobuf2-v4l2.h>
 
 #include "sun6i_video.h"
@@ -44,10 +45,7 @@ struct sun6i_csi_config {
 struct sun6i_csi_v4l2 {
 	struct v4l2_device		v4l2_dev;
 	struct media_device		media_dev;
-
 	struct v4l2_async_notifier	notifier;
-	/* video port settings */
-	struct v4l2_fwnode_endpoint	v4l2_ep;
 };
 
 struct sun6i_csi_device {
@@ -69,16 +67,25 @@ struct sun6i_csi_variant {
 	unsigned long	clock_mod_rate;
 };
 
+struct sun6i_csi_async_subdev {
+	struct v4l2_async_subdev	asd; /* must be first */
+
+	/* csi side video port settings for this subdev */
+	struct v4l2_fwnode_endpoint	vep;
+};
+
 /**
  * sun6i_csi_is_format_supported() - check if the format supported by csi
  * @csi_dev:	pointer to the csi device
  * @pixformat:	v4l2 pixel format (V4L2_PIX_FMT_*)
  * @mbus_code:	media bus format code (MEDIA_BUS_FMT_*)
+ * @vep:	parsed CSI side bus endpoint configuration
  *
  * Return: true if format is supported, false otherwise.
  */
 bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev,
-				   u32 pixformat, u32 mbus_code);
+				   u32 pixformat, u32 mbus_code,
+				   struct v4l2_fwnode_endpoint *vep);
 
 /**
  * sun6i_csi_set_power() - power on/off the csi
@@ -93,11 +100,13 @@ int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable);
  * sun6i_csi_update_config() - update the csi register settings
  * @csi_dev:	pointer to the csi device
  * @config:	see struct sun6i_csi_config
+ * @vep:	parsed CSI side bus endpoint configuration
  *
  * Return: 0 if successful, error code otherwise.
  */
 int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev,
-			    struct sun6i_csi_config *config);
+			    struct sun6i_csi_config *config,
+			    struct v4l2_fwnode_endpoint *vep);
 
 /**
  * sun6i_csi_update_buf_addr() - update the csi frame buffer address
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
index 82425fe1fb31..077875ec2dc4 100644
--- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
@@ -95,7 +95,8 @@ static void sun6i_video_buffer_configure(struct sun6i_csi_device *csi_dev,
 	sun6i_csi_update_buf_addr(csi_dev, csi_buffer->dma_addr);
 }
 
-static void sun6i_video_configure(struct sun6i_csi_device *csi_dev)
+static void sun6i_video_configure(struct sun6i_csi_device *csi_dev,
+				  struct v4l2_fwnode_endpoint *vep)
 {
 	struct sun6i_video *video = &csi_dev->video;
 	struct sun6i_csi_config config = { 0 };
@@ -106,7 +107,7 @@ static void sun6i_video_configure(struct sun6i_csi_device *csi_dev)
 	config.width = video->format.fmt.pix.width;
 	config.height = video->format.fmt.pix.height;
 
-	sun6i_csi_update_config(csi_dev, &config);
+	sun6i_csi_update_config(csi_dev, &config, vep);
 }
 
 /* Queue */
@@ -175,6 +176,7 @@ static int sun6i_video_start_streaming(struct vb2_queue *queue,
 	struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue);
 	struct sun6i_video *video = &csi_dev->video;
 	struct video_device *video_dev = &video->video_dev;
+	struct sun6i_csi_async_subdev *casd;
 	struct sun6i_csi_buffer *buf;
 	struct sun6i_csi_buffer *next_buf;
 	struct v4l2_subdev *subdev;
@@ -198,7 +200,9 @@ static int sun6i_video_start_streaming(struct vb2_queue *queue,
 		goto error_media_pipeline;
 	}
 
-	sun6i_video_configure(csi_dev);
+	casd = container_of(subdev->asd, struct sun6i_csi_async_subdev, asd);
+
+	sun6i_video_configure(csi_dev, &casd->vep);
 
 	spin_lock_irqsave(&video->dma_queue_lock, flags);
 
@@ -601,11 +605,16 @@ static const struct v4l2_file_operations sun6i_video_fops = {
 /* Media Entity */
 
 static int sun6i_video_link_validate_get_format(struct media_pad *pad,
-						struct v4l2_subdev_format *fmt)
+						struct v4l2_subdev_format *fmt,
+						struct v4l2_fwnode_endpoint **vep)
 {
 	if (is_media_entity_v4l2_subdev(pad->entity)) {
 		struct v4l2_subdev *sd =
 				media_entity_to_v4l2_subdev(pad->entity);
+		struct sun6i_csi_async_subdev *casd =
+			container_of(sd->asd, struct sun6i_csi_async_subdev, asd);
+
+		*vep = &casd->vep;
 
 		fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE;
 		fmt->pad = pad->index;
@@ -622,6 +631,7 @@ static int sun6i_video_link_validate(struct media_link *link)
 	struct sun6i_csi_device *csi_dev = video_get_drvdata(vdev);
 	struct sun6i_video *video = &csi_dev->video;
 	struct v4l2_subdev_format source_fmt;
+	struct v4l2_fwnode_endpoint *vep;
 	int ret;
 
 	video->mbus_code = 0;
@@ -632,13 +642,13 @@ static int sun6i_video_link_validate(struct media_link *link)
 		return -ENOLINK;
 	}
 
-	ret = sun6i_video_link_validate_get_format(link->source, &source_fmt);
+	ret = sun6i_video_link_validate_get_format(link->source, &source_fmt, &vep);
 	if (ret < 0)
 		return ret;
 
 	if (!sun6i_csi_is_format_supported(csi_dev,
 					   video->format.fmt.pix.pixelformat,
-					   source_fmt.format.code)) {
+					   source_fmt.format.code, vep)) {
 		dev_err(csi_dev->dev,
 			"Unsupported pixformat: 0x%x with mbus code: 0x%x!\n",
 			video->format.fmt.pix.pixelformat,
@@ -660,8 +670,20 @@ static int sun6i_video_link_validate(struct media_link *link)
 	return 0;
 }
 
+static int sun6i_video_link_setup(struct media_entity *entity,
+				  const struct media_pad *local,
+				  const struct media_pad *remote, u32 flags)
+{
+	/* Allow to enable one link only. */
+	if ((flags & MEDIA_LNK_FL_ENABLED) && media_pad_remote_pad_first(local))
+		return -EBUSY;
+
+	return 0;
+}
+
 static const struct media_entity_operations sun6i_video_media_ops = {
-	.link_validate = sun6i_video_link_validate
+	.link_validate = sun6i_video_link_validate,
+	.link_setup = sun6i_video_link_setup,
 };
 
 /* Video */
-- 
2.35.3

