162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * i.MX8 ISI - Input crossbar switch
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2022 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/device.h>
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/minmax.h>
1262306a36Sopenharmony_ci#include <linux/regmap.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/string.h>
1562306a36Sopenharmony_ci#include <linux/types.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <media/media-entity.h>
1862306a36Sopenharmony_ci#include <media/v4l2-subdev.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "imx8-isi-core.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic inline struct mxc_isi_crossbar *to_isi_crossbar(struct v4l2_subdev *sd)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	return container_of(sd, struct mxc_isi_crossbar, sd);
2562306a36Sopenharmony_ci}
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic int mxc_isi_crossbar_gasket_enable(struct mxc_isi_crossbar *xbar,
2862306a36Sopenharmony_ci					  struct v4l2_subdev_state *state,
2962306a36Sopenharmony_ci					  struct v4l2_subdev *remote_sd,
3062306a36Sopenharmony_ci					  u32 remote_pad, unsigned int port)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct mxc_isi_dev *isi = xbar->isi;
3362306a36Sopenharmony_ci	const struct mxc_gasket_ops *gasket_ops = isi->pdata->gasket_ops;
3462306a36Sopenharmony_ci	const struct v4l2_mbus_framefmt *fmt;
3562306a36Sopenharmony_ci	struct v4l2_mbus_frame_desc fd;
3662306a36Sopenharmony_ci	int ret;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (!gasket_ops)
3962306a36Sopenharmony_ci		return 0;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	/*
4262306a36Sopenharmony_ci	 * Configure and enable the gasket with the frame size and CSI-2 data
4362306a36Sopenharmony_ci	 * type. For YUV422 8-bit, enable dual component mode unconditionally,
4462306a36Sopenharmony_ci	 * to match the configuration of the CSIS.
4562306a36Sopenharmony_ci	 */
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	ret = v4l2_subdev_call(remote_sd, pad, get_frame_desc, remote_pad, &fd);
4862306a36Sopenharmony_ci	if (ret) {
4962306a36Sopenharmony_ci		dev_err(isi->dev,
5062306a36Sopenharmony_ci			"failed to get frame descriptor from '%s':%u: %d\n",
5162306a36Sopenharmony_ci			remote_sd->name, remote_pad, ret);
5262306a36Sopenharmony_ci		return ret;
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (fd.num_entries != 1) {
5662306a36Sopenharmony_ci		dev_err(isi->dev, "invalid frame descriptor for '%s':%u\n",
5762306a36Sopenharmony_ci			remote_sd->name, remote_pad);
5862306a36Sopenharmony_ci		return -EINVAL;
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	fmt = v4l2_subdev_state_get_stream_format(state, port, 0);
6262306a36Sopenharmony_ci	if (!fmt)
6362306a36Sopenharmony_ci		return -EINVAL;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	gasket_ops->enable(isi, &fd, fmt, port);
6662306a36Sopenharmony_ci	return 0;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic void mxc_isi_crossbar_gasket_disable(struct mxc_isi_crossbar *xbar,
7062306a36Sopenharmony_ci					    unsigned int port)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct mxc_isi_dev *isi = xbar->isi;
7362306a36Sopenharmony_ci	const struct mxc_gasket_ops *gasket_ops = isi->pdata->gasket_ops;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (!gasket_ops)
7662306a36Sopenharmony_ci		return;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	gasket_ops->disable(isi, port);
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
8262306a36Sopenharmony_ci * V4L2 subdev operations
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic const struct v4l2_mbus_framefmt mxc_isi_crossbar_default_format = {
8662306a36Sopenharmony_ci	.code = MXC_ISI_DEF_MBUS_CODE_SINK,
8762306a36Sopenharmony_ci	.width = MXC_ISI_DEF_WIDTH,
8862306a36Sopenharmony_ci	.height = MXC_ISI_DEF_HEIGHT,
8962306a36Sopenharmony_ci	.field = V4L2_FIELD_NONE,
9062306a36Sopenharmony_ci	.colorspace = MXC_ISI_DEF_COLOR_SPACE,
9162306a36Sopenharmony_ci	.ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC,
9262306a36Sopenharmony_ci	.quantization = MXC_ISI_DEF_QUANTIZATION,
9362306a36Sopenharmony_ci	.xfer_func = MXC_ISI_DEF_XFER_FUNC,
9462306a36Sopenharmony_ci};
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic int __mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
9762306a36Sopenharmony_ci					  struct v4l2_subdev_state *state,
9862306a36Sopenharmony_ci					  struct v4l2_subdev_krouting *routing)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
10162306a36Sopenharmony_ci	struct v4l2_subdev_route *route;
10262306a36Sopenharmony_ci	int ret;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	ret = v4l2_subdev_routing_validate(sd, routing,
10562306a36Sopenharmony_ci					   V4L2_SUBDEV_ROUTING_NO_N_TO_1);
10662306a36Sopenharmony_ci	if (ret)
10762306a36Sopenharmony_ci		return ret;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* The memory input can be routed to the first pipeline only. */
11062306a36Sopenharmony_ci	for_each_active_route(&state->routing, route) {
11162306a36Sopenharmony_ci		if (route->sink_pad == xbar->num_sinks - 1 &&
11262306a36Sopenharmony_ci		    route->source_pad != xbar->num_sinks) {
11362306a36Sopenharmony_ci			dev_dbg(xbar->isi->dev,
11462306a36Sopenharmony_ci				"invalid route from memory input (%u) to pipe %u\n",
11562306a36Sopenharmony_ci				route->sink_pad,
11662306a36Sopenharmony_ci				route->source_pad - xbar->num_sinks);
11762306a36Sopenharmony_ci			return -EINVAL;
11862306a36Sopenharmony_ci		}
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
12262306a36Sopenharmony_ci						&mxc_isi_crossbar_default_format);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic struct v4l2_subdev *
12662306a36Sopenharmony_cimxc_isi_crossbar_xlate_streams(struct mxc_isi_crossbar *xbar,
12762306a36Sopenharmony_ci			       struct v4l2_subdev_state *state,
12862306a36Sopenharmony_ci			       u32 source_pad, u64 source_streams,
12962306a36Sopenharmony_ci			       u32 *__sink_pad, u64 *__sink_streams,
13062306a36Sopenharmony_ci			       u32 *remote_pad)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct v4l2_subdev_route *route;
13362306a36Sopenharmony_ci	struct v4l2_subdev *sd;
13462306a36Sopenharmony_ci	struct media_pad *pad;
13562306a36Sopenharmony_ci	u64 sink_streams = 0;
13662306a36Sopenharmony_ci	int sink_pad = -1;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	/*
13962306a36Sopenharmony_ci	 * Translate the source pad and streams to the sink side. The routing
14062306a36Sopenharmony_ci	 * validation forbids stream merging, so all matching entries in the
14162306a36Sopenharmony_ci	 * routing table are guaranteed to have the same sink pad.
14262306a36Sopenharmony_ci	 *
14362306a36Sopenharmony_ci	 * TODO: This is likely worth a helper function, it could perhaps be
14462306a36Sopenharmony_ci	 * supported by v4l2_subdev_state_xlate_streams() with pad1 set to -1.
14562306a36Sopenharmony_ci	 */
14662306a36Sopenharmony_ci	for_each_active_route(&state->routing, route) {
14762306a36Sopenharmony_ci		if (route->source_pad != source_pad ||
14862306a36Sopenharmony_ci		    !(source_streams & BIT(route->source_stream)))
14962306a36Sopenharmony_ci			continue;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci		sink_streams |= BIT(route->sink_stream);
15262306a36Sopenharmony_ci		sink_pad = route->sink_pad;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (sink_pad < 0) {
15662306a36Sopenharmony_ci		dev_dbg(xbar->isi->dev,
15762306a36Sopenharmony_ci			"no stream connected to pipeline %u\n",
15862306a36Sopenharmony_ci			source_pad - xbar->num_sinks);
15962306a36Sopenharmony_ci		return ERR_PTR(-EPIPE);
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	pad = media_pad_remote_pad_first(&xbar->pads[sink_pad]);
16362306a36Sopenharmony_ci	sd = media_entity_to_v4l2_subdev(pad->entity);
16462306a36Sopenharmony_ci	if (!sd) {
16562306a36Sopenharmony_ci		dev_dbg(xbar->isi->dev,
16662306a36Sopenharmony_ci			"no entity connected to crossbar input %u\n",
16762306a36Sopenharmony_ci			sink_pad);
16862306a36Sopenharmony_ci		return ERR_PTR(-EPIPE);
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	*__sink_pad = sink_pad;
17262306a36Sopenharmony_ci	*__sink_streams = sink_streams;
17362306a36Sopenharmony_ci	*remote_pad = pad->index;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	return sd;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic int mxc_isi_crossbar_init_cfg(struct v4l2_subdev *sd,
17962306a36Sopenharmony_ci				     struct v4l2_subdev_state *state)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
18262306a36Sopenharmony_ci	struct v4l2_subdev_krouting routing = { };
18362306a36Sopenharmony_ci	struct v4l2_subdev_route *routes;
18462306a36Sopenharmony_ci	unsigned int i;
18562306a36Sopenharmony_ci	int ret;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/*
18862306a36Sopenharmony_ci	 * Create a 1:1 mapping between pixel link inputs and outputs to
18962306a36Sopenharmony_ci	 * pipelines by default.
19062306a36Sopenharmony_ci	 */
19162306a36Sopenharmony_ci	routes = kcalloc(xbar->num_sources, sizeof(*routes), GFP_KERNEL);
19262306a36Sopenharmony_ci	if (!routes)
19362306a36Sopenharmony_ci		return -ENOMEM;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	for (i = 0; i < xbar->num_sources; ++i) {
19662306a36Sopenharmony_ci		struct v4l2_subdev_route *route = &routes[i];
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		route->sink_pad = i;
19962306a36Sopenharmony_ci		route->source_pad = i + xbar->num_sinks;
20062306a36Sopenharmony_ci		route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	routing.num_routes = xbar->num_sources;
20462306a36Sopenharmony_ci	routing.routes = routes;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	ret = __mxc_isi_crossbar_set_routing(sd, state, &routing);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	kfree(routes);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	return ret;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev *sd,
21462306a36Sopenharmony_ci					   struct v4l2_subdev_state *state,
21562306a36Sopenharmony_ci					   struct v4l2_subdev_mbus_code_enum *code)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
21862306a36Sopenharmony_ci	const struct mxc_isi_bus_format_info *info;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	if (code->pad >= xbar->num_sinks) {
22162306a36Sopenharmony_ci		const struct v4l2_mbus_framefmt *format;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci		/*
22462306a36Sopenharmony_ci		 * The media bus code on source pads is identical to the
22562306a36Sopenharmony_ci		 * connected sink pad.
22662306a36Sopenharmony_ci		 */
22762306a36Sopenharmony_ci		if (code->index > 0)
22862306a36Sopenharmony_ci			return -EINVAL;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci		format = v4l2_subdev_state_get_opposite_stream_format(state,
23162306a36Sopenharmony_ci								      code->pad,
23262306a36Sopenharmony_ci								      code->stream);
23362306a36Sopenharmony_ci		if (!format)
23462306a36Sopenharmony_ci			return -EINVAL;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci		code->code = format->code;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		return 0;
23962306a36Sopenharmony_ci	}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	info = mxc_isi_bus_format_by_index(code->index, MXC_ISI_PIPE_PAD_SINK);
24262306a36Sopenharmony_ci	if (!info)
24362306a36Sopenharmony_ci		return -EINVAL;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	code->code = info->mbus_code;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return 0;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd,
25162306a36Sopenharmony_ci				    struct v4l2_subdev_state *state,
25262306a36Sopenharmony_ci				    struct v4l2_subdev_format *fmt)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
25562306a36Sopenharmony_ci	struct v4l2_mbus_framefmt *sink_fmt;
25662306a36Sopenharmony_ci	struct v4l2_subdev_route *route;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
25962306a36Sopenharmony_ci	    media_pad_is_streaming(&xbar->pads[fmt->pad]))
26062306a36Sopenharmony_ci		return -EBUSY;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	/*
26362306a36Sopenharmony_ci	 * The source pad format is always identical to the sink pad format and
26462306a36Sopenharmony_ci	 * can't be modified.
26562306a36Sopenharmony_ci	 */
26662306a36Sopenharmony_ci	if (fmt->pad >= xbar->num_sinks)
26762306a36Sopenharmony_ci		return v4l2_subdev_get_fmt(sd, state, fmt);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/* Validate the requested format. */
27062306a36Sopenharmony_ci	if (!mxc_isi_bus_format_by_code(fmt->format.code, MXC_ISI_PIPE_PAD_SINK))
27162306a36Sopenharmony_ci		fmt->format.code = MXC_ISI_DEF_MBUS_CODE_SINK;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	fmt->format.width = clamp_t(unsigned int, fmt->format.width,
27462306a36Sopenharmony_ci				    MXC_ISI_MIN_WIDTH, MXC_ISI_MAX_WIDTH_CHAINED);
27562306a36Sopenharmony_ci	fmt->format.height = clamp_t(unsigned int, fmt->format.height,
27662306a36Sopenharmony_ci				     MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT);
27762306a36Sopenharmony_ci	fmt->format.field = V4L2_FIELD_NONE;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/*
28062306a36Sopenharmony_ci	 * Set the format on the sink stream and propagate it to the source
28162306a36Sopenharmony_ci	 * streams.
28262306a36Sopenharmony_ci	 */
28362306a36Sopenharmony_ci	sink_fmt = v4l2_subdev_state_get_stream_format(state, fmt->pad,
28462306a36Sopenharmony_ci						       fmt->stream);
28562306a36Sopenharmony_ci	if (!sink_fmt)
28662306a36Sopenharmony_ci		return -EINVAL;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	*sink_fmt = fmt->format;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	/* TODO: A format propagation helper would be useful. */
29162306a36Sopenharmony_ci	for_each_active_route(&state->routing, route) {
29262306a36Sopenharmony_ci		struct v4l2_mbus_framefmt *source_fmt;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci		if (route->sink_pad != fmt->pad ||
29562306a36Sopenharmony_ci		    route->sink_stream != fmt->stream)
29662306a36Sopenharmony_ci			continue;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci		source_fmt = v4l2_subdev_state_get_stream_format(state, route->source_pad,
29962306a36Sopenharmony_ci								 route->source_stream);
30062306a36Sopenharmony_ci		if (!source_fmt)
30162306a36Sopenharmony_ci			return -EINVAL;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci		*source_fmt = fmt->format;
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	return 0;
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cistatic int mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
31062306a36Sopenharmony_ci					struct v4l2_subdev_state *state,
31162306a36Sopenharmony_ci					enum v4l2_subdev_format_whence which,
31262306a36Sopenharmony_ci					struct v4l2_subdev_krouting *routing)
31362306a36Sopenharmony_ci{
31462306a36Sopenharmony_ci	if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
31562306a36Sopenharmony_ci	    media_entity_is_streaming(&sd->entity))
31662306a36Sopenharmony_ci		return -EBUSY;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	return __mxc_isi_crossbar_set_routing(sd, state, routing);
31962306a36Sopenharmony_ci}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cistatic int mxc_isi_crossbar_enable_streams(struct v4l2_subdev *sd,
32262306a36Sopenharmony_ci					   struct v4l2_subdev_state *state,
32362306a36Sopenharmony_ci					   u32 pad, u64 streams_mask)
32462306a36Sopenharmony_ci{
32562306a36Sopenharmony_ci	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
32662306a36Sopenharmony_ci	struct v4l2_subdev *remote_sd;
32762306a36Sopenharmony_ci	struct mxc_isi_input *input;
32862306a36Sopenharmony_ci	u64 sink_streams;
32962306a36Sopenharmony_ci	u32 sink_pad;
33062306a36Sopenharmony_ci	u32 remote_pad;
33162306a36Sopenharmony_ci	int ret;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
33462306a36Sopenharmony_ci						   &sink_pad, &sink_streams,
33562306a36Sopenharmony_ci						   &remote_pad);
33662306a36Sopenharmony_ci	if (IS_ERR(remote_sd))
33762306a36Sopenharmony_ci		return PTR_ERR(remote_sd);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	input = &xbar->inputs[sink_pad];
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	/*
34262306a36Sopenharmony_ci	 * TODO: Track per-stream enable counts to support multiplexed
34362306a36Sopenharmony_ci	 * streams.
34462306a36Sopenharmony_ci	 */
34562306a36Sopenharmony_ci	if (!input->enable_count) {
34662306a36Sopenharmony_ci		ret = mxc_isi_crossbar_gasket_enable(xbar, state, remote_sd,
34762306a36Sopenharmony_ci						     remote_pad, sink_pad);
34862306a36Sopenharmony_ci		if (ret)
34962306a36Sopenharmony_ci			return ret;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci		ret = v4l2_subdev_enable_streams(remote_sd, remote_pad,
35262306a36Sopenharmony_ci						 sink_streams);
35362306a36Sopenharmony_ci		if (ret) {
35462306a36Sopenharmony_ci			dev_err(xbar->isi->dev,
35562306a36Sopenharmony_ci				"failed to %s streams 0x%llx on '%s':%u: %d\n",
35662306a36Sopenharmony_ci				"enable", sink_streams, remote_sd->name,
35762306a36Sopenharmony_ci				remote_pad, ret);
35862306a36Sopenharmony_ci			mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
35962306a36Sopenharmony_ci			return ret;
36062306a36Sopenharmony_ci		}
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	input->enable_count++;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	return 0;
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cistatic int mxc_isi_crossbar_disable_streams(struct v4l2_subdev *sd,
36962306a36Sopenharmony_ci					    struct v4l2_subdev_state *state,
37062306a36Sopenharmony_ci					    u32 pad, u64 streams_mask)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
37362306a36Sopenharmony_ci	struct v4l2_subdev *remote_sd;
37462306a36Sopenharmony_ci	struct mxc_isi_input *input;
37562306a36Sopenharmony_ci	u64 sink_streams;
37662306a36Sopenharmony_ci	u32 sink_pad;
37762306a36Sopenharmony_ci	u32 remote_pad;
37862306a36Sopenharmony_ci	int ret = 0;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
38162306a36Sopenharmony_ci						   &sink_pad, &sink_streams,
38262306a36Sopenharmony_ci						   &remote_pad);
38362306a36Sopenharmony_ci	if (IS_ERR(remote_sd))
38462306a36Sopenharmony_ci		return PTR_ERR(remote_sd);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	input = &xbar->inputs[sink_pad];
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	input->enable_count--;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	if (!input->enable_count) {
39162306a36Sopenharmony_ci		ret = v4l2_subdev_disable_streams(remote_sd, remote_pad,
39262306a36Sopenharmony_ci						  sink_streams);
39362306a36Sopenharmony_ci		if (ret)
39462306a36Sopenharmony_ci			dev_err(xbar->isi->dev,
39562306a36Sopenharmony_ci				"failed to %s streams 0x%llx on '%s':%u: %d\n",
39662306a36Sopenharmony_ci				"disable", sink_streams, remote_sd->name,
39762306a36Sopenharmony_ci				remote_pad, ret);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci		mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
40062306a36Sopenharmony_ci	}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	return ret;
40362306a36Sopenharmony_ci}
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_cistatic const struct v4l2_subdev_pad_ops mxc_isi_crossbar_subdev_pad_ops = {
40662306a36Sopenharmony_ci	.init_cfg = mxc_isi_crossbar_init_cfg,
40762306a36Sopenharmony_ci	.enum_mbus_code = mxc_isi_crossbar_enum_mbus_code,
40862306a36Sopenharmony_ci	.get_fmt = v4l2_subdev_get_fmt,
40962306a36Sopenharmony_ci	.set_fmt = mxc_isi_crossbar_set_fmt,
41062306a36Sopenharmony_ci	.set_routing = mxc_isi_crossbar_set_routing,
41162306a36Sopenharmony_ci	.enable_streams = mxc_isi_crossbar_enable_streams,
41262306a36Sopenharmony_ci	.disable_streams = mxc_isi_crossbar_disable_streams,
41362306a36Sopenharmony_ci};
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic const struct v4l2_subdev_ops mxc_isi_crossbar_subdev_ops = {
41662306a36Sopenharmony_ci	.pad = &mxc_isi_crossbar_subdev_pad_ops,
41762306a36Sopenharmony_ci};
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_cistatic const struct media_entity_operations mxc_isi_cross_entity_ops = {
42062306a36Sopenharmony_ci	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
42162306a36Sopenharmony_ci	.link_validate	= v4l2_subdev_link_validate,
42262306a36Sopenharmony_ci	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
42362306a36Sopenharmony_ci};
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
42662306a36Sopenharmony_ci * Init & cleanup
42762306a36Sopenharmony_ci */
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ciint mxc_isi_crossbar_init(struct mxc_isi_dev *isi)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	struct mxc_isi_crossbar *xbar = &isi->crossbar;
43262306a36Sopenharmony_ci	struct v4l2_subdev *sd = &xbar->sd;
43362306a36Sopenharmony_ci	unsigned int num_pads;
43462306a36Sopenharmony_ci	unsigned int i;
43562306a36Sopenharmony_ci	int ret;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	xbar->isi = isi;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	v4l2_subdev_init(sd, &mxc_isi_crossbar_subdev_ops);
44062306a36Sopenharmony_ci	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
44162306a36Sopenharmony_ci	strscpy(sd->name, "crossbar", sizeof(sd->name));
44262306a36Sopenharmony_ci	sd->dev = isi->dev;
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	sd->entity.function = MEDIA_ENT_F_VID_MUX;
44562306a36Sopenharmony_ci	sd->entity.ops = &mxc_isi_cross_entity_ops;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	/*
44862306a36Sopenharmony_ci	 * The subdev has one sink and one source per port, plus one sink for
44962306a36Sopenharmony_ci	 * the memory input.
45062306a36Sopenharmony_ci	 */
45162306a36Sopenharmony_ci	xbar->num_sinks = isi->pdata->num_ports + 1;
45262306a36Sopenharmony_ci	xbar->num_sources = isi->pdata->num_ports;
45362306a36Sopenharmony_ci	num_pads = xbar->num_sinks + xbar->num_sources;
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	xbar->pads = kcalloc(num_pads, sizeof(*xbar->pads), GFP_KERNEL);
45662306a36Sopenharmony_ci	if (!xbar->pads)
45762306a36Sopenharmony_ci		return -ENOMEM;
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	xbar->inputs = kcalloc(xbar->num_sinks, sizeof(*xbar->inputs),
46062306a36Sopenharmony_ci			       GFP_KERNEL);
46162306a36Sopenharmony_ci	if (!xbar->inputs) {
46262306a36Sopenharmony_ci		ret = -ENOMEM;
46362306a36Sopenharmony_ci		goto err_free;
46462306a36Sopenharmony_ci	}
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	for (i = 0; i < xbar->num_sinks; ++i)
46762306a36Sopenharmony_ci		xbar->pads[i].flags = MEDIA_PAD_FL_SINK
46862306a36Sopenharmony_ci				    | MEDIA_PAD_FL_MUST_CONNECT;
46962306a36Sopenharmony_ci	for (i = 0; i < xbar->num_sources; ++i)
47062306a36Sopenharmony_ci		xbar->pads[i + xbar->num_sinks].flags = MEDIA_PAD_FL_SOURCE;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	ret = media_entity_pads_init(&sd->entity, num_pads, xbar->pads);
47362306a36Sopenharmony_ci	if (ret)
47462306a36Sopenharmony_ci		goto err_free;
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	ret = v4l2_subdev_init_finalize(sd);
47762306a36Sopenharmony_ci	if (ret < 0)
47862306a36Sopenharmony_ci		goto err_entity;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	return 0;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cierr_entity:
48362306a36Sopenharmony_ci	media_entity_cleanup(&sd->entity);
48462306a36Sopenharmony_cierr_free:
48562306a36Sopenharmony_ci	kfree(xbar->pads);
48662306a36Sopenharmony_ci	kfree(xbar->inputs);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	return ret;
48962306a36Sopenharmony_ci}
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_civoid mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar)
49262306a36Sopenharmony_ci{
49362306a36Sopenharmony_ci	media_entity_cleanup(&xbar->sd.entity);
49462306a36Sopenharmony_ci	kfree(xbar->pads);
49562306a36Sopenharmony_ci	kfree(xbar->inputs);
49662306a36Sopenharmony_ci}
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ciint mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar)
49962306a36Sopenharmony_ci{
50062306a36Sopenharmony_ci	return v4l2_device_register_subdev(&xbar->isi->v4l2_dev, &xbar->sd);
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_civoid mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar)
50462306a36Sopenharmony_ci{
50562306a36Sopenharmony_ci}
506