162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * video stream multiplexer controlled via mux control 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2013 Pengutronix, Sascha Hauer <kernel@pengutronix.de> 662306a36Sopenharmony_ci * Copyright (C) 2016-2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/err.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/mutex.h> 1262306a36Sopenharmony_ci#include <linux/mux/consumer.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/of_graph.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <media/v4l2-async.h> 1862306a36Sopenharmony_ci#include <media/v4l2-device.h> 1962306a36Sopenharmony_ci#include <media/v4l2-fwnode.h> 2062306a36Sopenharmony_ci#include <media/v4l2-mc.h> 2162306a36Sopenharmony_ci#include <media/v4l2-subdev.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct video_mux { 2462306a36Sopenharmony_ci struct v4l2_subdev subdev; 2562306a36Sopenharmony_ci struct v4l2_async_notifier notifier; 2662306a36Sopenharmony_ci struct media_pad *pads; 2762306a36Sopenharmony_ci struct mux_control *mux; 2862306a36Sopenharmony_ci struct mutex lock; 2962306a36Sopenharmony_ci int active; 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic const struct v4l2_mbus_framefmt video_mux_format_mbus_default = { 3362306a36Sopenharmony_ci .width = 1, 3462306a36Sopenharmony_ci .height = 1, 3562306a36Sopenharmony_ci .code = MEDIA_BUS_FMT_Y8_1X8, 3662306a36Sopenharmony_ci .field = V4L2_FIELD_NONE, 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic inline struct video_mux * 4062306a36Sopenharmony_cinotifier_to_video_mux(struct v4l2_async_notifier *n) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci return container_of(n, struct video_mux, notifier); 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic inline struct video_mux *v4l2_subdev_to_video_mux(struct v4l2_subdev *sd) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci return container_of(sd, struct video_mux, subdev); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic int video_mux_link_setup(struct media_entity *entity, 5162306a36Sopenharmony_ci const struct media_pad *local, 5262306a36Sopenharmony_ci const struct media_pad *remote, u32 flags) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); 5562306a36Sopenharmony_ci struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); 5662306a36Sopenharmony_ci u16 source_pad = entity->num_pads - 1; 5762306a36Sopenharmony_ci int ret = 0; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci /* 6062306a36Sopenharmony_ci * The mux state is determined by the enabled sink pad link. 6162306a36Sopenharmony_ci * Enabling or disabling the source pad link has no effect. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci if (local->flags & MEDIA_PAD_FL_SOURCE) 6462306a36Sopenharmony_ci return 0; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]", 6762306a36Sopenharmony_ci remote->entity->name, remote->index, local->entity->name, 6862306a36Sopenharmony_ci local->index, flags & MEDIA_LNK_FL_ENABLED); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci mutex_lock(&vmux->lock); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if (flags & MEDIA_LNK_FL_ENABLED) { 7362306a36Sopenharmony_ci struct v4l2_subdev_state *sd_state; 7462306a36Sopenharmony_ci struct v4l2_mbus_framefmt *source_mbusformat; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (vmux->active == local->index) 7762306a36Sopenharmony_ci goto out; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (vmux->active >= 0) { 8062306a36Sopenharmony_ci ret = -EBUSY; 8162306a36Sopenharmony_ci goto out; 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci dev_dbg(sd->dev, "setting %d active\n", local->index); 8562306a36Sopenharmony_ci ret = mux_control_try_select(vmux->mux, local->index); 8662306a36Sopenharmony_ci if (ret < 0) 8762306a36Sopenharmony_ci goto out; 8862306a36Sopenharmony_ci vmux->active = local->index; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* Propagate the active format to the source */ 9162306a36Sopenharmony_ci sd_state = v4l2_subdev_lock_and_get_active_state(sd); 9262306a36Sopenharmony_ci source_mbusformat = v4l2_subdev_get_pad_format(sd, sd_state, 9362306a36Sopenharmony_ci source_pad); 9462306a36Sopenharmony_ci *source_mbusformat = *v4l2_subdev_get_pad_format(sd, sd_state, 9562306a36Sopenharmony_ci vmux->active); 9662306a36Sopenharmony_ci v4l2_subdev_unlock_state(sd_state); 9762306a36Sopenharmony_ci } else { 9862306a36Sopenharmony_ci if (vmux->active != local->index) 9962306a36Sopenharmony_ci goto out; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci dev_dbg(sd->dev, "going inactive\n"); 10262306a36Sopenharmony_ci mux_control_deselect(vmux->mux); 10362306a36Sopenharmony_ci vmux->active = -1; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ciout: 10762306a36Sopenharmony_ci mutex_unlock(&vmux->lock); 10862306a36Sopenharmony_ci return ret; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic const struct media_entity_operations video_mux_ops = { 11262306a36Sopenharmony_ci .link_setup = video_mux_link_setup, 11362306a36Sopenharmony_ci .link_validate = v4l2_subdev_link_validate, 11462306a36Sopenharmony_ci .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, 11562306a36Sopenharmony_ci}; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic int video_mux_s_stream(struct v4l2_subdev *sd, int enable) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); 12062306a36Sopenharmony_ci struct v4l2_subdev *upstream_sd; 12162306a36Sopenharmony_ci struct media_pad *pad; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (vmux->active == -1) { 12462306a36Sopenharmony_ci dev_err(sd->dev, "Can not start streaming on inactive mux\n"); 12562306a36Sopenharmony_ci return -EINVAL; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci pad = media_pad_remote_pad_first(&sd->entity.pads[vmux->active]); 12962306a36Sopenharmony_ci if (!pad) { 13062306a36Sopenharmony_ci dev_err(sd->dev, "Failed to find remote source pad\n"); 13162306a36Sopenharmony_ci return -ENOLINK; 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (!is_media_entity_v4l2_subdev(pad->entity)) { 13562306a36Sopenharmony_ci dev_err(sd->dev, "Upstream entity is not a v4l2 subdev\n"); 13662306a36Sopenharmony_ci return -ENODEV; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci upstream_sd = media_entity_to_v4l2_subdev(pad->entity); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci return v4l2_subdev_call(upstream_sd, video, s_stream, enable); 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic const struct v4l2_subdev_video_ops video_mux_subdev_video_ops = { 14562306a36Sopenharmony_ci .s_stream = video_mux_s_stream, 14662306a36Sopenharmony_ci}; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic int video_mux_set_format(struct v4l2_subdev *sd, 14962306a36Sopenharmony_ci struct v4l2_subdev_state *sd_state, 15062306a36Sopenharmony_ci struct v4l2_subdev_format *sdformat) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); 15362306a36Sopenharmony_ci struct v4l2_mbus_framefmt *mbusformat, *source_mbusformat; 15462306a36Sopenharmony_ci struct media_pad *pad = &vmux->pads[sdformat->pad]; 15562306a36Sopenharmony_ci u16 source_pad = sd->entity.num_pads - 1; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci mbusformat = v4l2_subdev_get_pad_format(sd, sd_state, sdformat->pad); 15862306a36Sopenharmony_ci if (!mbusformat) 15962306a36Sopenharmony_ci return -EINVAL; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci source_mbusformat = v4l2_subdev_get_pad_format(sd, sd_state, source_pad); 16262306a36Sopenharmony_ci if (!source_mbusformat) 16362306a36Sopenharmony_ci return -EINVAL; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* No size limitations except V4L2 compliance requirements */ 16662306a36Sopenharmony_ci v4l_bound_align_image(&sdformat->format.width, 1, 65536, 0, 16762306a36Sopenharmony_ci &sdformat->format.height, 1, 65536, 0, 0); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* All formats except LVDS and vendor specific formats are acceptable */ 17062306a36Sopenharmony_ci switch (sdformat->format.code) { 17162306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB444_1X12: 17262306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE: 17362306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE: 17462306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: 17562306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: 17662306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB565_1X16: 17762306a36Sopenharmony_ci case MEDIA_BUS_FMT_BGR565_2X8_BE: 17862306a36Sopenharmony_ci case MEDIA_BUS_FMT_BGR565_2X8_LE: 17962306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB565_2X8_BE: 18062306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB565_2X8_LE: 18162306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB666_1X18: 18262306a36Sopenharmony_ci case MEDIA_BUS_FMT_RBG888_1X24: 18362306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: 18462306a36Sopenharmony_ci case MEDIA_BUS_FMT_BGR888_1X24: 18562306a36Sopenharmony_ci case MEDIA_BUS_FMT_GBR888_1X24: 18662306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_1X24: 18762306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_2X12_BE: 18862306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_2X12_LE: 18962306a36Sopenharmony_ci case MEDIA_BUS_FMT_ARGB8888_1X32: 19062306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_1X32_PADHI: 19162306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB101010_1X30: 19262306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB121212_1X36: 19362306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB161616_1X48: 19462306a36Sopenharmony_ci case MEDIA_BUS_FMT_Y8_1X8: 19562306a36Sopenharmony_ci case MEDIA_BUS_FMT_UV8_1X8: 19662306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYVY8_1_5X8: 19762306a36Sopenharmony_ci case MEDIA_BUS_FMT_VYUY8_1_5X8: 19862306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUYV8_1_5X8: 19962306a36Sopenharmony_ci case MEDIA_BUS_FMT_YVYU8_1_5X8: 20062306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYVY8_2X8: 20162306a36Sopenharmony_ci case MEDIA_BUS_FMT_VYUY8_2X8: 20262306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUYV8_2X8: 20362306a36Sopenharmony_ci case MEDIA_BUS_FMT_YVYU8_2X8: 20462306a36Sopenharmony_ci case MEDIA_BUS_FMT_Y10_1X10: 20562306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYVY10_2X10: 20662306a36Sopenharmony_ci case MEDIA_BUS_FMT_VYUY10_2X10: 20762306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUYV10_2X10: 20862306a36Sopenharmony_ci case MEDIA_BUS_FMT_YVYU10_2X10: 20962306a36Sopenharmony_ci case MEDIA_BUS_FMT_Y12_1X12: 21062306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYVY12_2X12: 21162306a36Sopenharmony_ci case MEDIA_BUS_FMT_VYUY12_2X12: 21262306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUYV12_2X12: 21362306a36Sopenharmony_ci case MEDIA_BUS_FMT_YVYU12_2X12: 21462306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYVY8_1X16: 21562306a36Sopenharmony_ci case MEDIA_BUS_FMT_VYUY8_1X16: 21662306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUYV8_1X16: 21762306a36Sopenharmony_ci case MEDIA_BUS_FMT_YVYU8_1X16: 21862306a36Sopenharmony_ci case MEDIA_BUS_FMT_YDYUYDYV8_1X16: 21962306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYVY10_1X20: 22062306a36Sopenharmony_ci case MEDIA_BUS_FMT_VYUY10_1X20: 22162306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUYV10_1X20: 22262306a36Sopenharmony_ci case MEDIA_BUS_FMT_YVYU10_1X20: 22362306a36Sopenharmony_ci case MEDIA_BUS_FMT_VUY8_1X24: 22462306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUV8_1X24: 22562306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYYVYY8_0_5X24: 22662306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYVY12_1X24: 22762306a36Sopenharmony_ci case MEDIA_BUS_FMT_VYUY12_1X24: 22862306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUYV12_1X24: 22962306a36Sopenharmony_ci case MEDIA_BUS_FMT_YVYU12_1X24: 23062306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUV10_1X30: 23162306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYYVYY10_0_5X30: 23262306a36Sopenharmony_ci case MEDIA_BUS_FMT_AYUV8_1X32: 23362306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYYVYY12_0_5X36: 23462306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUV12_1X36: 23562306a36Sopenharmony_ci case MEDIA_BUS_FMT_YUV16_1X48: 23662306a36Sopenharmony_ci case MEDIA_BUS_FMT_UYYVYY16_0_5X48: 23762306a36Sopenharmony_ci case MEDIA_BUS_FMT_JPEG_1X8: 23862306a36Sopenharmony_ci case MEDIA_BUS_FMT_AHSV8888_1X32: 23962306a36Sopenharmony_ci case MEDIA_BUS_FMT_SBGGR8_1X8: 24062306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGBRG8_1X8: 24162306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGRBG8_1X8: 24262306a36Sopenharmony_ci case MEDIA_BUS_FMT_SRGGB8_1X8: 24362306a36Sopenharmony_ci case MEDIA_BUS_FMT_SBGGR10_1X10: 24462306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGBRG10_1X10: 24562306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGRBG10_1X10: 24662306a36Sopenharmony_ci case MEDIA_BUS_FMT_SRGGB10_1X10: 24762306a36Sopenharmony_ci case MEDIA_BUS_FMT_SBGGR12_1X12: 24862306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGBRG12_1X12: 24962306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGRBG12_1X12: 25062306a36Sopenharmony_ci case MEDIA_BUS_FMT_SRGGB12_1X12: 25162306a36Sopenharmony_ci case MEDIA_BUS_FMT_SBGGR14_1X14: 25262306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGBRG14_1X14: 25362306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGRBG14_1X14: 25462306a36Sopenharmony_ci case MEDIA_BUS_FMT_SRGGB14_1X14: 25562306a36Sopenharmony_ci case MEDIA_BUS_FMT_SBGGR16_1X16: 25662306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGBRG16_1X16: 25762306a36Sopenharmony_ci case MEDIA_BUS_FMT_SGRBG16_1X16: 25862306a36Sopenharmony_ci case MEDIA_BUS_FMT_SRGGB16_1X16: 25962306a36Sopenharmony_ci break; 26062306a36Sopenharmony_ci default: 26162306a36Sopenharmony_ci sdformat->format.code = MEDIA_BUS_FMT_Y8_1X8; 26262306a36Sopenharmony_ci break; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci if (sdformat->format.field == V4L2_FIELD_ANY) 26562306a36Sopenharmony_ci sdformat->format.field = V4L2_FIELD_NONE; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci mutex_lock(&vmux->lock); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci /* Source pad mirrors active sink pad, no limitations on sink pads */ 27062306a36Sopenharmony_ci if ((pad->flags & MEDIA_PAD_FL_SOURCE) && vmux->active >= 0) 27162306a36Sopenharmony_ci sdformat->format = *v4l2_subdev_get_pad_format(sd, sd_state, 27262306a36Sopenharmony_ci vmux->active); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci *mbusformat = sdformat->format; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* Propagate the format from an active sink to source */ 27762306a36Sopenharmony_ci if ((pad->flags & MEDIA_PAD_FL_SINK) && (pad->index == vmux->active)) 27862306a36Sopenharmony_ci *source_mbusformat = sdformat->format; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci mutex_unlock(&vmux->lock); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return 0; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic int video_mux_init_cfg(struct v4l2_subdev *sd, 28662306a36Sopenharmony_ci struct v4l2_subdev_state *sd_state) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); 28962306a36Sopenharmony_ci struct v4l2_mbus_framefmt *mbusformat; 29062306a36Sopenharmony_ci unsigned int i; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci mutex_lock(&vmux->lock); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci for (i = 0; i < sd->entity.num_pads; i++) { 29562306a36Sopenharmony_ci mbusformat = v4l2_subdev_get_pad_format(sd, sd_state, i); 29662306a36Sopenharmony_ci *mbusformat = video_mux_format_mbus_default; 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci mutex_unlock(&vmux->lock); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci return 0; 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic const struct v4l2_subdev_pad_ops video_mux_pad_ops = { 30562306a36Sopenharmony_ci .init_cfg = video_mux_init_cfg, 30662306a36Sopenharmony_ci .get_fmt = v4l2_subdev_get_fmt, 30762306a36Sopenharmony_ci .set_fmt = video_mux_set_format, 30862306a36Sopenharmony_ci}; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic const struct v4l2_subdev_ops video_mux_subdev_ops = { 31162306a36Sopenharmony_ci .pad = &video_mux_pad_ops, 31262306a36Sopenharmony_ci .video = &video_mux_subdev_video_ops, 31362306a36Sopenharmony_ci}; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cistatic int video_mux_notify_bound(struct v4l2_async_notifier *notifier, 31662306a36Sopenharmony_ci struct v4l2_subdev *sd, 31762306a36Sopenharmony_ci struct v4l2_async_connection *asd) 31862306a36Sopenharmony_ci{ 31962306a36Sopenharmony_ci struct video_mux *vmux = notifier_to_video_mux(notifier); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci return v4l2_create_fwnode_links(sd, &vmux->subdev); 32262306a36Sopenharmony_ci} 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_cistatic const struct v4l2_async_notifier_operations video_mux_notify_ops = { 32562306a36Sopenharmony_ci .bound = video_mux_notify_bound, 32662306a36Sopenharmony_ci}; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic int video_mux_async_register(struct video_mux *vmux, 32962306a36Sopenharmony_ci unsigned int num_input_pads) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci unsigned int i; 33262306a36Sopenharmony_ci int ret; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci v4l2_async_subdev_nf_init(&vmux->notifier, &vmux->subdev); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci for (i = 0; i < num_input_pads; i++) { 33762306a36Sopenharmony_ci struct v4l2_async_connection *asd; 33862306a36Sopenharmony_ci struct fwnode_handle *ep, *remote_ep; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci ep = fwnode_graph_get_endpoint_by_id( 34162306a36Sopenharmony_ci dev_fwnode(vmux->subdev.dev), i, 0, 34262306a36Sopenharmony_ci FWNODE_GRAPH_ENDPOINT_NEXT); 34362306a36Sopenharmony_ci if (!ep) 34462306a36Sopenharmony_ci continue; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci /* Skip dangling endpoints for backwards compatibility */ 34762306a36Sopenharmony_ci remote_ep = fwnode_graph_get_remote_endpoint(ep); 34862306a36Sopenharmony_ci if (!remote_ep) { 34962306a36Sopenharmony_ci fwnode_handle_put(ep); 35062306a36Sopenharmony_ci continue; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci fwnode_handle_put(remote_ep); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci asd = v4l2_async_nf_add_fwnode_remote(&vmux->notifier, ep, 35562306a36Sopenharmony_ci struct v4l2_async_connection); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci fwnode_handle_put(ep); 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci if (IS_ERR(asd)) { 36062306a36Sopenharmony_ci ret = PTR_ERR(asd); 36162306a36Sopenharmony_ci /* OK if asd already exists */ 36262306a36Sopenharmony_ci if (ret != -EEXIST) 36362306a36Sopenharmony_ci goto err_nf_cleanup; 36462306a36Sopenharmony_ci } 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci vmux->notifier.ops = &video_mux_notify_ops; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci ret = v4l2_async_nf_register(&vmux->notifier); 37062306a36Sopenharmony_ci if (ret) 37162306a36Sopenharmony_ci goto err_nf_cleanup; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci ret = v4l2_async_register_subdev(&vmux->subdev); 37462306a36Sopenharmony_ci if (ret) 37562306a36Sopenharmony_ci goto err_nf_unregister; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci return 0; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cierr_nf_unregister: 38062306a36Sopenharmony_ci v4l2_async_nf_unregister(&vmux->notifier); 38162306a36Sopenharmony_cierr_nf_cleanup: 38262306a36Sopenharmony_ci v4l2_async_nf_cleanup(&vmux->notifier); 38362306a36Sopenharmony_ci return ret; 38462306a36Sopenharmony_ci} 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_cistatic int video_mux_probe(struct platform_device *pdev) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 38962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 39062306a36Sopenharmony_ci struct device_node *ep; 39162306a36Sopenharmony_ci struct video_mux *vmux; 39262306a36Sopenharmony_ci unsigned int num_pads = 0; 39362306a36Sopenharmony_ci unsigned int i; 39462306a36Sopenharmony_ci int ret; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci vmux = devm_kzalloc(dev, sizeof(*vmux), GFP_KERNEL); 39762306a36Sopenharmony_ci if (!vmux) 39862306a36Sopenharmony_ci return -ENOMEM; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci platform_set_drvdata(pdev, vmux); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci v4l2_subdev_init(&vmux->subdev, &video_mux_subdev_ops); 40362306a36Sopenharmony_ci snprintf(vmux->subdev.name, sizeof(vmux->subdev.name), "%pOFn", np); 40462306a36Sopenharmony_ci vmux->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; 40562306a36Sopenharmony_ci vmux->subdev.dev = dev; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci /* 40862306a36Sopenharmony_ci * The largest numbered port is the output port. It determines 40962306a36Sopenharmony_ci * total number of pads. 41062306a36Sopenharmony_ci */ 41162306a36Sopenharmony_ci for_each_endpoint_of_node(np, ep) { 41262306a36Sopenharmony_ci struct of_endpoint endpoint; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci of_graph_parse_endpoint(ep, &endpoint); 41562306a36Sopenharmony_ci num_pads = max(num_pads, endpoint.port + 1); 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci if (num_pads < 2) { 41962306a36Sopenharmony_ci dev_err(dev, "Not enough ports %d\n", num_pads); 42062306a36Sopenharmony_ci return -EINVAL; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci vmux->mux = devm_mux_control_get(dev, NULL); 42462306a36Sopenharmony_ci if (IS_ERR(vmux->mux)) { 42562306a36Sopenharmony_ci ret = PTR_ERR(vmux->mux); 42662306a36Sopenharmony_ci return dev_err_probe(dev, ret, "Failed to get mux\n"); 42762306a36Sopenharmony_ci } 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci mutex_init(&vmux->lock); 43062306a36Sopenharmony_ci vmux->active = -1; 43162306a36Sopenharmony_ci vmux->pads = devm_kcalloc(dev, num_pads, sizeof(*vmux->pads), 43262306a36Sopenharmony_ci GFP_KERNEL); 43362306a36Sopenharmony_ci if (!vmux->pads) 43462306a36Sopenharmony_ci return -ENOMEM; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci for (i = 0; i < num_pads; i++) 43762306a36Sopenharmony_ci vmux->pads[i].flags = (i < num_pads - 1) ? MEDIA_PAD_FL_SINK 43862306a36Sopenharmony_ci : MEDIA_PAD_FL_SOURCE; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci vmux->subdev.entity.function = MEDIA_ENT_F_VID_MUX; 44162306a36Sopenharmony_ci ret = media_entity_pads_init(&vmux->subdev.entity, num_pads, 44262306a36Sopenharmony_ci vmux->pads); 44362306a36Sopenharmony_ci if (ret < 0) 44462306a36Sopenharmony_ci return ret; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci vmux->subdev.entity.ops = &video_mux_ops; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci ret = v4l2_subdev_init_finalize(&vmux->subdev); 44962306a36Sopenharmony_ci if (ret < 0) 45062306a36Sopenharmony_ci goto err_entity_cleanup; 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci ret = video_mux_async_register(vmux, num_pads - 1); 45362306a36Sopenharmony_ci if (ret) 45462306a36Sopenharmony_ci goto err_subdev_cleanup; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci return 0; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cierr_subdev_cleanup: 45962306a36Sopenharmony_ci v4l2_subdev_cleanup(&vmux->subdev); 46062306a36Sopenharmony_cierr_entity_cleanup: 46162306a36Sopenharmony_ci media_entity_cleanup(&vmux->subdev.entity); 46262306a36Sopenharmony_ci return ret; 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_cistatic void video_mux_remove(struct platform_device *pdev) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci struct video_mux *vmux = platform_get_drvdata(pdev); 46862306a36Sopenharmony_ci struct v4l2_subdev *sd = &vmux->subdev; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci v4l2_async_nf_unregister(&vmux->notifier); 47162306a36Sopenharmony_ci v4l2_async_nf_cleanup(&vmux->notifier); 47262306a36Sopenharmony_ci v4l2_async_unregister_subdev(sd); 47362306a36Sopenharmony_ci v4l2_subdev_cleanup(sd); 47462306a36Sopenharmony_ci media_entity_cleanup(&sd->entity); 47562306a36Sopenharmony_ci} 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_cistatic const struct of_device_id video_mux_dt_ids[] = { 47862306a36Sopenharmony_ci { .compatible = "video-mux", }, 47962306a36Sopenharmony_ci { /* sentinel */ } 48062306a36Sopenharmony_ci}; 48162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, video_mux_dt_ids); 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_cistatic struct platform_driver video_mux_driver = { 48462306a36Sopenharmony_ci .probe = video_mux_probe, 48562306a36Sopenharmony_ci .remove_new = video_mux_remove, 48662306a36Sopenharmony_ci .driver = { 48762306a36Sopenharmony_ci .of_match_table = video_mux_dt_ids, 48862306a36Sopenharmony_ci .name = "video-mux", 48962306a36Sopenharmony_ci }, 49062306a36Sopenharmony_ci}; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_cimodule_platform_driver(video_mux_driver); 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ciMODULE_DESCRIPTION("video stream multiplexer"); 49562306a36Sopenharmony_ciMODULE_AUTHOR("Sascha Hauer, Pengutronix"); 49662306a36Sopenharmony_ciMODULE_AUTHOR("Philipp Zabel, Pengutronix"); 49762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 498