18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * video stream multiplexer controlled via mux control
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2013 Pengutronix, Sascha Hauer <kernel@pengutronix.de>
68c2ecf20Sopenharmony_ci * Copyright (C) 2016-2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/err.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/mutex.h>
128c2ecf20Sopenharmony_ci#include <linux/mux/consumer.h>
138c2ecf20Sopenharmony_ci#include <linux/of.h>
148c2ecf20Sopenharmony_ci#include <linux/of_graph.h>
158c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
168c2ecf20Sopenharmony_ci#include <linux/slab.h>
178c2ecf20Sopenharmony_ci#include <media/v4l2-async.h>
188c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
198c2ecf20Sopenharmony_ci#include <media/v4l2-fwnode.h>
208c2ecf20Sopenharmony_ci#include <media/v4l2-mc.h>
218c2ecf20Sopenharmony_ci#include <media/v4l2-subdev.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistruct video_mux {
248c2ecf20Sopenharmony_ci	struct v4l2_subdev subdev;
258c2ecf20Sopenharmony_ci	struct v4l2_async_notifier notifier;
268c2ecf20Sopenharmony_ci	struct media_pad *pads;
278c2ecf20Sopenharmony_ci	struct v4l2_mbus_framefmt *format_mbus;
288c2ecf20Sopenharmony_ci	struct mux_control *mux;
298c2ecf20Sopenharmony_ci	struct mutex lock;
308c2ecf20Sopenharmony_ci	int active;
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic const struct v4l2_mbus_framefmt video_mux_format_mbus_default = {
348c2ecf20Sopenharmony_ci	.width = 1,
358c2ecf20Sopenharmony_ci	.height = 1,
368c2ecf20Sopenharmony_ci	.code = MEDIA_BUS_FMT_Y8_1X8,
378c2ecf20Sopenharmony_ci	.field = V4L2_FIELD_NONE,
388c2ecf20Sopenharmony_ci};
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic inline struct video_mux *
418c2ecf20Sopenharmony_cinotifier_to_video_mux(struct v4l2_async_notifier *n)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	return container_of(n, struct video_mux, notifier);
448c2ecf20Sopenharmony_ci}
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic inline struct video_mux *v4l2_subdev_to_video_mux(struct v4l2_subdev *sd)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	return container_of(sd, struct video_mux, subdev);
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int video_mux_link_setup(struct media_entity *entity,
528c2ecf20Sopenharmony_ci				const struct media_pad *local,
538c2ecf20Sopenharmony_ci				const struct media_pad *remote, u32 flags)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
568c2ecf20Sopenharmony_ci	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
578c2ecf20Sopenharmony_ci	u16 source_pad = entity->num_pads - 1;
588c2ecf20Sopenharmony_ci	int ret = 0;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	/*
618c2ecf20Sopenharmony_ci	 * The mux state is determined by the enabled sink pad link.
628c2ecf20Sopenharmony_ci	 * Enabling or disabling the source pad link has no effect.
638c2ecf20Sopenharmony_ci	 */
648c2ecf20Sopenharmony_ci	if (local->flags & MEDIA_PAD_FL_SOURCE)
658c2ecf20Sopenharmony_ci		return 0;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]",
688c2ecf20Sopenharmony_ci		remote->entity->name, remote->index, local->entity->name,
698c2ecf20Sopenharmony_ci		local->index, flags & MEDIA_LNK_FL_ENABLED);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	mutex_lock(&vmux->lock);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	if (flags & MEDIA_LNK_FL_ENABLED) {
748c2ecf20Sopenharmony_ci		if (vmux->active == local->index)
758c2ecf20Sopenharmony_ci			goto out;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci		if (vmux->active >= 0) {
788c2ecf20Sopenharmony_ci			ret = -EBUSY;
798c2ecf20Sopenharmony_ci			goto out;
808c2ecf20Sopenharmony_ci		}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci		dev_dbg(sd->dev, "setting %d active\n", local->index);
838c2ecf20Sopenharmony_ci		ret = mux_control_try_select(vmux->mux, local->index);
848c2ecf20Sopenharmony_ci		if (ret < 0)
858c2ecf20Sopenharmony_ci			goto out;
868c2ecf20Sopenharmony_ci		vmux->active = local->index;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci		/* Propagate the active format to the source */
898c2ecf20Sopenharmony_ci		vmux->format_mbus[source_pad] = vmux->format_mbus[vmux->active];
908c2ecf20Sopenharmony_ci	} else {
918c2ecf20Sopenharmony_ci		if (vmux->active != local->index)
928c2ecf20Sopenharmony_ci			goto out;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci		dev_dbg(sd->dev, "going inactive\n");
958c2ecf20Sopenharmony_ci		mux_control_deselect(vmux->mux);
968c2ecf20Sopenharmony_ci		vmux->active = -1;
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ciout:
1008c2ecf20Sopenharmony_ci	mutex_unlock(&vmux->lock);
1018c2ecf20Sopenharmony_ci	return ret;
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cistatic const struct media_entity_operations video_mux_ops = {
1058c2ecf20Sopenharmony_ci	.link_setup = video_mux_link_setup,
1068c2ecf20Sopenharmony_ci	.link_validate = v4l2_subdev_link_validate,
1078c2ecf20Sopenharmony_ci	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
1088c2ecf20Sopenharmony_ci};
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic int video_mux_s_stream(struct v4l2_subdev *sd, int enable)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
1138c2ecf20Sopenharmony_ci	struct v4l2_subdev *upstream_sd;
1148c2ecf20Sopenharmony_ci	struct media_pad *pad;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	if (vmux->active == -1) {
1178c2ecf20Sopenharmony_ci		dev_err(sd->dev, "Can not start streaming on inactive mux\n");
1188c2ecf20Sopenharmony_ci		return -EINVAL;
1198c2ecf20Sopenharmony_ci	}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	pad = media_entity_remote_pad(&sd->entity.pads[vmux->active]);
1228c2ecf20Sopenharmony_ci	if (!pad) {
1238c2ecf20Sopenharmony_ci		dev_err(sd->dev, "Failed to find remote source pad\n");
1248c2ecf20Sopenharmony_ci		return -ENOLINK;
1258c2ecf20Sopenharmony_ci	}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	if (!is_media_entity_v4l2_subdev(pad->entity)) {
1288c2ecf20Sopenharmony_ci		dev_err(sd->dev, "Upstream entity is not a v4l2 subdev\n");
1298c2ecf20Sopenharmony_ci		return -ENODEV;
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	upstream_sd = media_entity_to_v4l2_subdev(pad->entity);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	return v4l2_subdev_call(upstream_sd, video, s_stream, enable);
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_video_ops video_mux_subdev_video_ops = {
1388c2ecf20Sopenharmony_ci	.s_stream = video_mux_s_stream,
1398c2ecf20Sopenharmony_ci};
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic struct v4l2_mbus_framefmt *
1428c2ecf20Sopenharmony_ci__video_mux_get_pad_format(struct v4l2_subdev *sd,
1438c2ecf20Sopenharmony_ci			   struct v4l2_subdev_pad_config *cfg,
1448c2ecf20Sopenharmony_ci			   unsigned int pad, u32 which)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	switch (which) {
1498c2ecf20Sopenharmony_ci	case V4L2_SUBDEV_FORMAT_TRY:
1508c2ecf20Sopenharmony_ci		return v4l2_subdev_get_try_format(sd, cfg, pad);
1518c2ecf20Sopenharmony_ci	case V4L2_SUBDEV_FORMAT_ACTIVE:
1528c2ecf20Sopenharmony_ci		return &vmux->format_mbus[pad];
1538c2ecf20Sopenharmony_ci	default:
1548c2ecf20Sopenharmony_ci		return NULL;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistatic int video_mux_get_format(struct v4l2_subdev *sd,
1598c2ecf20Sopenharmony_ci			    struct v4l2_subdev_pad_config *cfg,
1608c2ecf20Sopenharmony_ci			    struct v4l2_subdev_format *sdformat)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	mutex_lock(&vmux->lock);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	sdformat->format = *__video_mux_get_pad_format(sd, cfg, sdformat->pad,
1678c2ecf20Sopenharmony_ci						       sdformat->which);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	mutex_unlock(&vmux->lock);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	return 0;
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic int video_mux_set_format(struct v4l2_subdev *sd,
1758c2ecf20Sopenharmony_ci			    struct v4l2_subdev_pad_config *cfg,
1768c2ecf20Sopenharmony_ci			    struct v4l2_subdev_format *sdformat)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
1798c2ecf20Sopenharmony_ci	struct v4l2_mbus_framefmt *mbusformat, *source_mbusformat;
1808c2ecf20Sopenharmony_ci	struct media_pad *pad = &vmux->pads[sdformat->pad];
1818c2ecf20Sopenharmony_ci	u16 source_pad = sd->entity.num_pads - 1;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	mbusformat = __video_mux_get_pad_format(sd, cfg, sdformat->pad,
1848c2ecf20Sopenharmony_ci					    sdformat->which);
1858c2ecf20Sopenharmony_ci	if (!mbusformat)
1868c2ecf20Sopenharmony_ci		return -EINVAL;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	source_mbusformat = __video_mux_get_pad_format(sd, cfg, source_pad,
1898c2ecf20Sopenharmony_ci						       sdformat->which);
1908c2ecf20Sopenharmony_ci	if (!source_mbusformat)
1918c2ecf20Sopenharmony_ci		return -EINVAL;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	/* No size limitations except V4L2 compliance requirements */
1948c2ecf20Sopenharmony_ci	v4l_bound_align_image(&sdformat->format.width, 1, 65536, 0,
1958c2ecf20Sopenharmony_ci			      &sdformat->format.height, 1, 65536, 0, 0);
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	/* All formats except LVDS and vendor specific formats are acceptable */
1988c2ecf20Sopenharmony_ci	switch (sdformat->format.code) {
1998c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB444_1X12:
2008c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
2018c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
2028c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
2038c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
2048c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB565_1X16:
2058c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_BGR565_2X8_BE:
2068c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_BGR565_2X8_LE:
2078c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB565_2X8_BE:
2088c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB565_2X8_LE:
2098c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB666_1X18:
2108c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RBG888_1X24:
2118c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
2128c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_BGR888_1X24:
2138c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_GBR888_1X24:
2148c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_1X24:
2158c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_2X12_BE:
2168c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_2X12_LE:
2178c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_ARGB8888_1X32:
2188c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_1X32_PADHI:
2198c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB101010_1X30:
2208c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB121212_1X36:
2218c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_RGB161616_1X48:
2228c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_Y8_1X8:
2238c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UV8_1X8:
2248c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYVY8_1_5X8:
2258c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VYUY8_1_5X8:
2268c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUYV8_1_5X8:
2278c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YVYU8_1_5X8:
2288c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYVY8_2X8:
2298c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VYUY8_2X8:
2308c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUYV8_2X8:
2318c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YVYU8_2X8:
2328c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_Y10_1X10:
2338c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYVY10_2X10:
2348c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VYUY10_2X10:
2358c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUYV10_2X10:
2368c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YVYU10_2X10:
2378c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_Y12_1X12:
2388c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYVY12_2X12:
2398c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VYUY12_2X12:
2408c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUYV12_2X12:
2418c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YVYU12_2X12:
2428c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYVY8_1X16:
2438c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VYUY8_1X16:
2448c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUYV8_1X16:
2458c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YVYU8_1X16:
2468c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YDYUYDYV8_1X16:
2478c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYVY10_1X20:
2488c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VYUY10_1X20:
2498c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUYV10_1X20:
2508c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YVYU10_1X20:
2518c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VUY8_1X24:
2528c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUV8_1X24:
2538c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
2548c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYVY12_1X24:
2558c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_VYUY12_1X24:
2568c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUYV12_1X24:
2578c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YVYU12_1X24:
2588c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUV10_1X30:
2598c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
2608c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_AYUV8_1X32:
2618c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYYVYY12_0_5X36:
2628c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUV12_1X36:
2638c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_YUV16_1X48:
2648c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_UYYVYY16_0_5X48:
2658c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_JPEG_1X8:
2668c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_AHSV8888_1X32:
2678c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SBGGR8_1X8:
2688c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGBRG8_1X8:
2698c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGRBG8_1X8:
2708c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SRGGB8_1X8:
2718c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SBGGR10_1X10:
2728c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGBRG10_1X10:
2738c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGRBG10_1X10:
2748c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SRGGB10_1X10:
2758c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SBGGR12_1X12:
2768c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGBRG12_1X12:
2778c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGRBG12_1X12:
2788c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SRGGB12_1X12:
2798c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SBGGR14_1X14:
2808c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGBRG14_1X14:
2818c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGRBG14_1X14:
2828c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SRGGB14_1X14:
2838c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SBGGR16_1X16:
2848c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGBRG16_1X16:
2858c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SGRBG16_1X16:
2868c2ecf20Sopenharmony_ci	case MEDIA_BUS_FMT_SRGGB16_1X16:
2878c2ecf20Sopenharmony_ci		break;
2888c2ecf20Sopenharmony_ci	default:
2898c2ecf20Sopenharmony_ci		sdformat->format.code = MEDIA_BUS_FMT_Y8_1X8;
2908c2ecf20Sopenharmony_ci		break;
2918c2ecf20Sopenharmony_ci	}
2928c2ecf20Sopenharmony_ci	if (sdformat->format.field == V4L2_FIELD_ANY)
2938c2ecf20Sopenharmony_ci		sdformat->format.field = V4L2_FIELD_NONE;
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	mutex_lock(&vmux->lock);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	/* Source pad mirrors active sink pad, no limitations on sink pads */
2988c2ecf20Sopenharmony_ci	if ((pad->flags & MEDIA_PAD_FL_SOURCE) && vmux->active >= 0)
2998c2ecf20Sopenharmony_ci		sdformat->format = vmux->format_mbus[vmux->active];
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	*mbusformat = sdformat->format;
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci	/* Propagate the format from an active sink to source */
3048c2ecf20Sopenharmony_ci	if ((pad->flags & MEDIA_PAD_FL_SINK) && (pad->index == vmux->active))
3058c2ecf20Sopenharmony_ci		*source_mbusformat = sdformat->format;
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci	mutex_unlock(&vmux->lock);
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	return 0;
3108c2ecf20Sopenharmony_ci}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_cistatic int video_mux_init_cfg(struct v4l2_subdev *sd,
3138c2ecf20Sopenharmony_ci			      struct v4l2_subdev_pad_config *cfg)
3148c2ecf20Sopenharmony_ci{
3158c2ecf20Sopenharmony_ci	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
3168c2ecf20Sopenharmony_ci	struct v4l2_mbus_framefmt *mbusformat;
3178c2ecf20Sopenharmony_ci	unsigned int i;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	mutex_lock(&vmux->lock);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	for (i = 0; i < sd->entity.num_pads; i++) {
3228c2ecf20Sopenharmony_ci		mbusformat = v4l2_subdev_get_try_format(sd, cfg, i);
3238c2ecf20Sopenharmony_ci		*mbusformat = video_mux_format_mbus_default;
3248c2ecf20Sopenharmony_ci	}
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	mutex_unlock(&vmux->lock);
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	return 0;
3298c2ecf20Sopenharmony_ci}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_pad_ops video_mux_pad_ops = {
3328c2ecf20Sopenharmony_ci	.init_cfg = video_mux_init_cfg,
3338c2ecf20Sopenharmony_ci	.get_fmt = video_mux_get_format,
3348c2ecf20Sopenharmony_ci	.set_fmt = video_mux_set_format,
3358c2ecf20Sopenharmony_ci};
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_ops video_mux_subdev_ops = {
3388c2ecf20Sopenharmony_ci	.pad = &video_mux_pad_ops,
3398c2ecf20Sopenharmony_ci	.video = &video_mux_subdev_video_ops,
3408c2ecf20Sopenharmony_ci};
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_cistatic int video_mux_notify_bound(struct v4l2_async_notifier *notifier,
3438c2ecf20Sopenharmony_ci				  struct v4l2_subdev *sd,
3448c2ecf20Sopenharmony_ci				  struct v4l2_async_subdev *asd)
3458c2ecf20Sopenharmony_ci{
3468c2ecf20Sopenharmony_ci	struct video_mux *vmux = notifier_to_video_mux(notifier);
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	return v4l2_create_fwnode_links(sd, &vmux->subdev);
3498c2ecf20Sopenharmony_ci}
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_cistatic const struct v4l2_async_notifier_operations video_mux_notify_ops = {
3528c2ecf20Sopenharmony_ci	.bound = video_mux_notify_bound,
3538c2ecf20Sopenharmony_ci};
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_cistatic int video_mux_async_register(struct video_mux *vmux,
3568c2ecf20Sopenharmony_ci				    unsigned int num_input_pads)
3578c2ecf20Sopenharmony_ci{
3588c2ecf20Sopenharmony_ci	unsigned int i;
3598c2ecf20Sopenharmony_ci	int ret;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	v4l2_async_notifier_init(&vmux->notifier);
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	for (i = 0; i < num_input_pads; i++) {
3648c2ecf20Sopenharmony_ci		struct v4l2_async_subdev *asd;
3658c2ecf20Sopenharmony_ci		struct fwnode_handle *ep, *remote_ep;
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci		ep = fwnode_graph_get_endpoint_by_id(
3688c2ecf20Sopenharmony_ci			dev_fwnode(vmux->subdev.dev), i, 0,
3698c2ecf20Sopenharmony_ci			FWNODE_GRAPH_ENDPOINT_NEXT);
3708c2ecf20Sopenharmony_ci		if (!ep)
3718c2ecf20Sopenharmony_ci			continue;
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci		/* Skip dangling endpoints for backwards compatibility */
3748c2ecf20Sopenharmony_ci		remote_ep = fwnode_graph_get_remote_endpoint(ep);
3758c2ecf20Sopenharmony_ci		if (!remote_ep) {
3768c2ecf20Sopenharmony_ci			fwnode_handle_put(ep);
3778c2ecf20Sopenharmony_ci			continue;
3788c2ecf20Sopenharmony_ci		}
3798c2ecf20Sopenharmony_ci		fwnode_handle_put(remote_ep);
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci		asd = v4l2_async_notifier_add_fwnode_remote_subdev(
3828c2ecf20Sopenharmony_ci			&vmux->notifier, ep, sizeof(*asd));
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci		fwnode_handle_put(ep);
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci		if (IS_ERR(asd)) {
3878c2ecf20Sopenharmony_ci			ret = PTR_ERR(asd);
3888c2ecf20Sopenharmony_ci			/* OK if asd already exists */
3898c2ecf20Sopenharmony_ci			if (ret != -EEXIST)
3908c2ecf20Sopenharmony_ci				return ret;
3918c2ecf20Sopenharmony_ci		}
3928c2ecf20Sopenharmony_ci	}
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	vmux->notifier.ops = &video_mux_notify_ops;
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	ret = v4l2_async_subdev_notifier_register(&vmux->subdev,
3978c2ecf20Sopenharmony_ci						  &vmux->notifier);
3988c2ecf20Sopenharmony_ci	if (ret)
3998c2ecf20Sopenharmony_ci		return ret;
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	return v4l2_async_register_subdev(&vmux->subdev);
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic int video_mux_probe(struct platform_device *pdev)
4058c2ecf20Sopenharmony_ci{
4068c2ecf20Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
4078c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
4088c2ecf20Sopenharmony_ci	struct device_node *ep;
4098c2ecf20Sopenharmony_ci	struct video_mux *vmux;
4108c2ecf20Sopenharmony_ci	unsigned int num_pads = 0;
4118c2ecf20Sopenharmony_ci	unsigned int i;
4128c2ecf20Sopenharmony_ci	int ret;
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_ci	vmux = devm_kzalloc(dev, sizeof(*vmux), GFP_KERNEL);
4158c2ecf20Sopenharmony_ci	if (!vmux)
4168c2ecf20Sopenharmony_ci		return -ENOMEM;
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, vmux);
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	v4l2_subdev_init(&vmux->subdev, &video_mux_subdev_ops);
4218c2ecf20Sopenharmony_ci	snprintf(vmux->subdev.name, sizeof(vmux->subdev.name), "%pOFn", np);
4228c2ecf20Sopenharmony_ci	vmux->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
4238c2ecf20Sopenharmony_ci	vmux->subdev.dev = dev;
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_ci	/*
4268c2ecf20Sopenharmony_ci	 * The largest numbered port is the output port. It determines
4278c2ecf20Sopenharmony_ci	 * total number of pads.
4288c2ecf20Sopenharmony_ci	 */
4298c2ecf20Sopenharmony_ci	for_each_endpoint_of_node(np, ep) {
4308c2ecf20Sopenharmony_ci		struct of_endpoint endpoint;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci		of_graph_parse_endpoint(ep, &endpoint);
4338c2ecf20Sopenharmony_ci		num_pads = max(num_pads, endpoint.port + 1);
4348c2ecf20Sopenharmony_ci	}
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	if (num_pads < 2) {
4378c2ecf20Sopenharmony_ci		dev_err(dev, "Not enough ports %d\n", num_pads);
4388c2ecf20Sopenharmony_ci		return -EINVAL;
4398c2ecf20Sopenharmony_ci	}
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	vmux->mux = devm_mux_control_get(dev, NULL);
4428c2ecf20Sopenharmony_ci	if (IS_ERR(vmux->mux)) {
4438c2ecf20Sopenharmony_ci		ret = PTR_ERR(vmux->mux);
4448c2ecf20Sopenharmony_ci		if (ret != -EPROBE_DEFER)
4458c2ecf20Sopenharmony_ci			dev_err(dev, "Failed to get mux: %d\n", ret);
4468c2ecf20Sopenharmony_ci		return ret;
4478c2ecf20Sopenharmony_ci	}
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci	mutex_init(&vmux->lock);
4508c2ecf20Sopenharmony_ci	vmux->active = -1;
4518c2ecf20Sopenharmony_ci	vmux->pads = devm_kcalloc(dev, num_pads, sizeof(*vmux->pads),
4528c2ecf20Sopenharmony_ci				  GFP_KERNEL);
4538c2ecf20Sopenharmony_ci	if (!vmux->pads)
4548c2ecf20Sopenharmony_ci		return -ENOMEM;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	vmux->format_mbus = devm_kcalloc(dev, num_pads,
4578c2ecf20Sopenharmony_ci					 sizeof(*vmux->format_mbus),
4588c2ecf20Sopenharmony_ci					 GFP_KERNEL);
4598c2ecf20Sopenharmony_ci	if (!vmux->format_mbus)
4608c2ecf20Sopenharmony_ci		return -ENOMEM;
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	for (i = 0; i < num_pads; i++) {
4638c2ecf20Sopenharmony_ci		vmux->pads[i].flags = (i < num_pads - 1) ? MEDIA_PAD_FL_SINK
4648c2ecf20Sopenharmony_ci							 : MEDIA_PAD_FL_SOURCE;
4658c2ecf20Sopenharmony_ci		vmux->format_mbus[i] = video_mux_format_mbus_default;
4668c2ecf20Sopenharmony_ci	}
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ci	vmux->subdev.entity.function = MEDIA_ENT_F_VID_MUX;
4698c2ecf20Sopenharmony_ci	ret = media_entity_pads_init(&vmux->subdev.entity, num_pads,
4708c2ecf20Sopenharmony_ci				     vmux->pads);
4718c2ecf20Sopenharmony_ci	if (ret < 0)
4728c2ecf20Sopenharmony_ci		return ret;
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci	vmux->subdev.entity.ops = &video_mux_ops;
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	ret = video_mux_async_register(vmux, num_pads - 1);
4778c2ecf20Sopenharmony_ci	if (ret) {
4788c2ecf20Sopenharmony_ci		v4l2_async_notifier_unregister(&vmux->notifier);
4798c2ecf20Sopenharmony_ci		v4l2_async_notifier_cleanup(&vmux->notifier);
4808c2ecf20Sopenharmony_ci	}
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ci	return ret;
4838c2ecf20Sopenharmony_ci}
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_cistatic int video_mux_remove(struct platform_device *pdev)
4868c2ecf20Sopenharmony_ci{
4878c2ecf20Sopenharmony_ci	struct video_mux *vmux = platform_get_drvdata(pdev);
4888c2ecf20Sopenharmony_ci	struct v4l2_subdev *sd = &vmux->subdev;
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	v4l2_async_notifier_unregister(&vmux->notifier);
4918c2ecf20Sopenharmony_ci	v4l2_async_notifier_cleanup(&vmux->notifier);
4928c2ecf20Sopenharmony_ci	v4l2_async_unregister_subdev(sd);
4938c2ecf20Sopenharmony_ci	media_entity_cleanup(&sd->entity);
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	return 0;
4968c2ecf20Sopenharmony_ci}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_cistatic const struct of_device_id video_mux_dt_ids[] = {
4998c2ecf20Sopenharmony_ci	{ .compatible = "video-mux", },
5008c2ecf20Sopenharmony_ci	{ /* sentinel */ }
5018c2ecf20Sopenharmony_ci};
5028c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, video_mux_dt_ids);
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_cistatic struct platform_driver video_mux_driver = {
5058c2ecf20Sopenharmony_ci	.probe		= video_mux_probe,
5068c2ecf20Sopenharmony_ci	.remove		= video_mux_remove,
5078c2ecf20Sopenharmony_ci	.driver		= {
5088c2ecf20Sopenharmony_ci		.of_match_table = video_mux_dt_ids,
5098c2ecf20Sopenharmony_ci		.name = "video-mux",
5108c2ecf20Sopenharmony_ci	},
5118c2ecf20Sopenharmony_ci};
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_cimodule_platform_driver(video_mux_driver);
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("video stream multiplexer");
5168c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sascha Hauer, Pengutronix");
5178c2ecf20Sopenharmony_ciMODULE_AUTHOR("Philipp Zabel, Pengutronix");
5188c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
519