xref: /linux/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c (revision 0526b56cbc3c489642bd6a5fe4b718dea7ef0ee8)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * i.MX8 ISI - Input crossbar switch
4  *
5  * Copyright (c) 2022 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
6  */
7 
8 #include <linux/device.h>
9 #include <linux/errno.h>
10 #include <linux/kernel.h>
11 #include <linux/minmax.h>
12 #include <linux/regmap.h>
13 #include <linux/slab.h>
14 #include <linux/string.h>
15 #include <linux/types.h>
16 
17 #include <media/media-entity.h>
18 #include <media/mipi-csi2.h>
19 #include <media/v4l2-subdev.h>
20 
21 #include "imx8-isi-core.h"
22 
23 static inline struct mxc_isi_crossbar *to_isi_crossbar(struct v4l2_subdev *sd)
24 {
25 	return container_of(sd, struct mxc_isi_crossbar, sd);
26 }
27 
28 /* -----------------------------------------------------------------------------
29  * Media block control (i.MX8MN and i.MX8MP only)
30  */
31 #define GASKET_BASE(n)				(0x0060 + (n) * 0x30)
32 
33 #define GASKET_CTRL				0x0000
34 #define GASKET_CTRL_DATA_TYPE(dt)		((dt) << 8)
35 #define GASKET_CTRL_DATA_TYPE_MASK		(0x3f << 8)
36 #define GASKET_CTRL_DUAL_COMP_ENABLE		BIT(1)
37 #define GASKET_CTRL_ENABLE			BIT(0)
38 
39 #define GASKET_HSIZE				0x0004
40 #define GASKET_VSIZE				0x0008
41 
42 static int mxc_isi_crossbar_gasket_enable(struct mxc_isi_crossbar *xbar,
43 					  struct v4l2_subdev_state *state,
44 					  struct v4l2_subdev *remote_sd,
45 					  u32 remote_pad, unsigned int port)
46 {
47 	struct mxc_isi_dev *isi = xbar->isi;
48 	const struct v4l2_mbus_framefmt *fmt;
49 	struct v4l2_mbus_frame_desc fd;
50 	u32 val;
51 	int ret;
52 
53 	if (!isi->pdata->has_gasket)
54 		return 0;
55 
56 	/*
57 	 * Configure and enable the gasket with the frame size and CSI-2 data
58 	 * type. For YUV422 8-bit, enable dual component mode unconditionally,
59 	 * to match the configuration of the CSIS.
60 	 */
61 
62 	ret = v4l2_subdev_call(remote_sd, pad, get_frame_desc, remote_pad, &fd);
63 	if (ret) {
64 		dev_err(isi->dev,
65 			"failed to get frame descriptor from '%s':%u: %d\n",
66 			remote_sd->name, remote_pad, ret);
67 		return ret;
68 	}
69 
70 	if (fd.num_entries != 1) {
71 		dev_err(isi->dev, "invalid frame descriptor for '%s':%u\n",
72 			remote_sd->name, remote_pad);
73 		return -EINVAL;
74 	}
75 
76 	fmt = v4l2_subdev_state_get_stream_format(state, port, 0);
77 	if (!fmt)
78 		return -EINVAL;
79 
80 	regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_HSIZE, fmt->width);
81 	regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_VSIZE, fmt->height);
82 
83 	val = GASKET_CTRL_DATA_TYPE(fd.entry[0].bus.csi2.dt)
84 	    | GASKET_CTRL_ENABLE;
85 
86 	if (fd.entry[0].bus.csi2.dt == MIPI_CSI2_DT_YUV422_8B)
87 		val |= GASKET_CTRL_DUAL_COMP_ENABLE;
88 
89 	regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, val);
90 
91 	return 0;
92 }
93 
94 static void mxc_isi_crossbar_gasket_disable(struct mxc_isi_crossbar *xbar,
95 					    unsigned int port)
96 {
97 	struct mxc_isi_dev *isi = xbar->isi;
98 
99 	if (!isi->pdata->has_gasket)
100 		return;
101 
102 	regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, 0);
103 }
104 
105 /* -----------------------------------------------------------------------------
106  * V4L2 subdev operations
107  */
108 
109 static const struct v4l2_mbus_framefmt mxc_isi_crossbar_default_format = {
110 	.code = MXC_ISI_DEF_MBUS_CODE_SINK,
111 	.width = MXC_ISI_DEF_WIDTH,
112 	.height = MXC_ISI_DEF_HEIGHT,
113 	.field = V4L2_FIELD_NONE,
114 	.colorspace = MXC_ISI_DEF_COLOR_SPACE,
115 	.ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC,
116 	.quantization = MXC_ISI_DEF_QUANTIZATION,
117 	.xfer_func = MXC_ISI_DEF_XFER_FUNC,
118 };
119 
120 static int __mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
121 					  struct v4l2_subdev_state *state,
122 					  struct v4l2_subdev_krouting *routing)
123 {
124 	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
125 	struct v4l2_subdev_route *route;
126 	int ret;
127 
128 	ret = v4l2_subdev_routing_validate(sd, routing,
129 					   V4L2_SUBDEV_ROUTING_NO_N_TO_1);
130 	if (ret)
131 		return ret;
132 
133 	/* The memory input can be routed to the first pipeline only. */
134 	for_each_active_route(&state->routing, route) {
135 		if (route->sink_pad == xbar->num_sinks - 1 &&
136 		    route->source_pad != xbar->num_sinks) {
137 			dev_dbg(xbar->isi->dev,
138 				"invalid route from memory input (%u) to pipe %u\n",
139 				route->sink_pad,
140 				route->source_pad - xbar->num_sinks);
141 			return -EINVAL;
142 		}
143 	}
144 
145 	return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
146 						&mxc_isi_crossbar_default_format);
147 }
148 
149 static struct v4l2_subdev *
150 mxc_isi_crossbar_xlate_streams(struct mxc_isi_crossbar *xbar,
151 			       struct v4l2_subdev_state *state,
152 			       u32 source_pad, u64 source_streams,
153 			       u32 *__sink_pad, u64 *__sink_streams,
154 			       u32 *remote_pad)
155 {
156 	struct v4l2_subdev_route *route;
157 	struct v4l2_subdev *sd;
158 	struct media_pad *pad;
159 	u64 sink_streams = 0;
160 	int sink_pad = -1;
161 
162 	/*
163 	 * Translate the source pad and streams to the sink side. The routing
164 	 * validation forbids stream merging, so all matching entries in the
165 	 * routing table are guaranteed to have the same sink pad.
166 	 *
167 	 * TODO: This is likely worth a helper function, it could perhaps be
168 	 * supported by v4l2_subdev_state_xlate_streams() with pad1 set to -1.
169 	 */
170 	for_each_active_route(&state->routing, route) {
171 		if (route->source_pad != source_pad ||
172 		    !(source_streams & BIT(route->source_stream)))
173 			continue;
174 
175 		sink_streams |= BIT(route->sink_stream);
176 		sink_pad = route->sink_pad;
177 	}
178 
179 	if (sink_pad < 0) {
180 		dev_dbg(xbar->isi->dev,
181 			"no stream connected to pipeline %u\n",
182 			source_pad - xbar->num_sinks);
183 		return ERR_PTR(-EPIPE);
184 	}
185 
186 	pad = media_pad_remote_pad_first(&xbar->pads[sink_pad]);
187 	sd = media_entity_to_v4l2_subdev(pad->entity);
188 
189 	if (!sd) {
190 		dev_dbg(xbar->isi->dev,
191 			"no entity connected to crossbar input %u\n",
192 			sink_pad);
193 		return ERR_PTR(-EPIPE);
194 	}
195 
196 	*__sink_pad = sink_pad;
197 	*__sink_streams = sink_streams;
198 	*remote_pad = pad->index;
199 
200 	return sd;
201 }
202 
203 static int mxc_isi_crossbar_init_cfg(struct v4l2_subdev *sd,
204 				     struct v4l2_subdev_state *state)
205 {
206 	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
207 	struct v4l2_subdev_krouting routing = { };
208 	struct v4l2_subdev_route *routes;
209 	unsigned int i;
210 	int ret;
211 
212 	/*
213 	 * Create a 1:1 mapping between pixel link inputs and outputs to
214 	 * pipelines by default.
215 	 */
216 	routes = kcalloc(xbar->num_sources, sizeof(*routes), GFP_KERNEL);
217 	if (!routes)
218 		return -ENOMEM;
219 
220 	for (i = 0; i < xbar->num_sources; ++i) {
221 		struct v4l2_subdev_route *route = &routes[i];
222 
223 		route->sink_pad = i;
224 		route->source_pad = i + xbar->num_sinks;
225 		route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
226 	};
227 
228 	routing.num_routes = xbar->num_sources;
229 	routing.routes = routes;
230 
231 	ret = __mxc_isi_crossbar_set_routing(sd, state, &routing);
232 
233 	kfree(routes);
234 
235 	return ret;
236 }
237 
238 static int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev *sd,
239 					   struct v4l2_subdev_state *state,
240 					   struct v4l2_subdev_mbus_code_enum *code)
241 {
242 	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
243 	const struct mxc_isi_bus_format_info *info;
244 
245 	if (code->pad >= xbar->num_sinks) {
246 		const struct v4l2_mbus_framefmt *format;
247 
248 		/*
249 		 * The media bus code on source pads is identical to the
250 		 * connected sink pad.
251 		 */
252 		if (code->index > 0)
253 			return -EINVAL;
254 
255 		format = v4l2_subdev_state_get_opposite_stream_format(state,
256 								      code->pad,
257 								      code->stream);
258 		if (!format)
259 			return -EINVAL;
260 
261 		code->code = format->code;
262 
263 		return 0;
264 	}
265 
266 	info = mxc_isi_bus_format_by_index(code->index, MXC_ISI_PIPE_PAD_SINK);
267 	if (!info)
268 		return -EINVAL;
269 
270 	code->code = info->mbus_code;
271 
272 	return 0;
273 }
274 
275 static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd,
276 				    struct v4l2_subdev_state *state,
277 				    struct v4l2_subdev_format *fmt)
278 {
279 	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
280 	struct v4l2_mbus_framefmt *sink_fmt;
281 	struct v4l2_subdev_route *route;
282 
283 	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
284 	    media_pad_is_streaming(&xbar->pads[fmt->pad]))
285 		return -EBUSY;
286 
287 	/*
288 	 * The source pad format is always identical to the sink pad format and
289 	 * can't be modified.
290 	 */
291 	if (fmt->pad >= xbar->num_sinks)
292 		return v4l2_subdev_get_fmt(sd, state, fmt);
293 
294 	/* Validate the requested format. */
295 	if (!mxc_isi_bus_format_by_code(fmt->format.code, MXC_ISI_PIPE_PAD_SINK))
296 		fmt->format.code = MXC_ISI_DEF_MBUS_CODE_SINK;
297 
298 	fmt->format.width = clamp_t(unsigned int, fmt->format.width,
299 				    MXC_ISI_MIN_WIDTH, MXC_ISI_MAX_WIDTH_CHAINED);
300 	fmt->format.height = clamp_t(unsigned int, fmt->format.height,
301 				     MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT);
302 	fmt->format.field = V4L2_FIELD_NONE;
303 
304 	/*
305 	 * Set the format on the sink stream and propagate it to the source
306 	 * streams.
307 	 */
308 	sink_fmt = v4l2_subdev_state_get_stream_format(state, fmt->pad,
309 						       fmt->stream);
310 	if (!sink_fmt)
311 		return -EINVAL;
312 
313 	*sink_fmt = fmt->format;
314 
315 	/* TODO: A format propagation helper would be useful. */
316 	for_each_active_route(&state->routing, route) {
317 		struct v4l2_mbus_framefmt *source_fmt;
318 
319 		if (route->sink_pad != fmt->pad ||
320 		    route->sink_stream != fmt->stream)
321 			continue;
322 
323 		source_fmt = v4l2_subdev_state_get_stream_format(state, route->source_pad,
324 								 route->source_stream);
325 		if (!source_fmt)
326 			return -EINVAL;
327 
328 		*source_fmt = fmt->format;
329 	}
330 
331 	return 0;
332 }
333 
334 static int mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
335 					struct v4l2_subdev_state *state,
336 					enum v4l2_subdev_format_whence which,
337 					struct v4l2_subdev_krouting *routing)
338 {
339 	if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
340 	    media_entity_is_streaming(&sd->entity))
341 		return -EBUSY;
342 
343 	return __mxc_isi_crossbar_set_routing(sd, state, routing);
344 }
345 
346 static int mxc_isi_crossbar_enable_streams(struct v4l2_subdev *sd,
347 					   struct v4l2_subdev_state *state,
348 					   u32 pad, u64 streams_mask)
349 {
350 	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
351 	struct v4l2_subdev *remote_sd;
352 	struct mxc_isi_input *input;
353 	u64 sink_streams;
354 	u32 sink_pad;
355 	u32 remote_pad;
356 	int ret;
357 
358 	remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
359 						   &sink_pad, &sink_streams,
360 						   &remote_pad);
361 	if (IS_ERR(remote_sd))
362 		return PTR_ERR(remote_sd);
363 
364 	input = &xbar->inputs[sink_pad];
365 
366 	/*
367 	 * TODO: Track per-stream enable counts to support multiplexed
368 	 * streams.
369 	 */
370 	if (!input->enable_count) {
371 		ret = mxc_isi_crossbar_gasket_enable(xbar, state, remote_sd,
372 						     remote_pad, sink_pad);
373 		if (ret)
374 			return ret;
375 
376 		ret = v4l2_subdev_enable_streams(remote_sd, remote_pad,
377 						 sink_streams);
378 		if (ret) {
379 			dev_err(xbar->isi->dev,
380 				"failed to %s streams 0x%llx on '%s':%u: %d\n",
381 				"enable", sink_streams, remote_sd->name,
382 				remote_pad, ret);
383 			mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
384 			return ret;
385 		}
386 	}
387 
388 	input->enable_count++;
389 
390 	return 0;
391 }
392 
393 static int mxc_isi_crossbar_disable_streams(struct v4l2_subdev *sd,
394 					    struct v4l2_subdev_state *state,
395 					    u32 pad, u64 streams_mask)
396 {
397 	struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
398 	struct v4l2_subdev *remote_sd;
399 	struct mxc_isi_input *input;
400 	u64 sink_streams;
401 	u32 sink_pad;
402 	u32 remote_pad;
403 	int ret = 0;
404 
405 	remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
406 						   &sink_pad, &sink_streams,
407 						   &remote_pad);
408 	if (IS_ERR(remote_sd))
409 		return PTR_ERR(remote_sd);
410 
411 	input = &xbar->inputs[sink_pad];
412 
413 	input->enable_count--;
414 
415 	if (!input->enable_count) {
416 		ret = v4l2_subdev_disable_streams(remote_sd, remote_pad,
417 						  sink_streams);
418 		if (ret)
419 			dev_err(xbar->isi->dev,
420 				"failed to %s streams 0x%llx on '%s':%u: %d\n",
421 				"disable", sink_streams, remote_sd->name,
422 				remote_pad, ret);
423 
424 		mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
425 	}
426 
427 	return ret;
428 }
429 
430 static const struct v4l2_subdev_pad_ops mxc_isi_crossbar_subdev_pad_ops = {
431 	.init_cfg = mxc_isi_crossbar_init_cfg,
432 	.enum_mbus_code = mxc_isi_crossbar_enum_mbus_code,
433 	.get_fmt = v4l2_subdev_get_fmt,
434 	.set_fmt = mxc_isi_crossbar_set_fmt,
435 	.set_routing = mxc_isi_crossbar_set_routing,
436 	.enable_streams = mxc_isi_crossbar_enable_streams,
437 	.disable_streams = mxc_isi_crossbar_disable_streams,
438 };
439 
440 static const struct v4l2_subdev_ops mxc_isi_crossbar_subdev_ops = {
441 	.pad = &mxc_isi_crossbar_subdev_pad_ops,
442 };
443 
444 static const struct media_entity_operations mxc_isi_cross_entity_ops = {
445 	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
446 	.link_validate	= v4l2_subdev_link_validate,
447 	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
448 };
449 
450 /* -----------------------------------------------------------------------------
451  * Init & cleanup
452  */
453 
454 int mxc_isi_crossbar_init(struct mxc_isi_dev *isi)
455 {
456 	struct mxc_isi_crossbar *xbar = &isi->crossbar;
457 	struct v4l2_subdev *sd = &xbar->sd;
458 	unsigned int num_pads;
459 	unsigned int i;
460 	int ret;
461 
462 	xbar->isi = isi;
463 
464 	v4l2_subdev_init(sd, &mxc_isi_crossbar_subdev_ops);
465 	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
466 	strscpy(sd->name, "crossbar", sizeof(sd->name));
467 	sd->dev = isi->dev;
468 
469 	sd->entity.function = MEDIA_ENT_F_VID_MUX;
470 	sd->entity.ops = &mxc_isi_cross_entity_ops;
471 
472 	/*
473 	 * The subdev has one sink and one source per port, plus one sink for
474 	 * the memory input.
475 	 */
476 	xbar->num_sinks = isi->pdata->num_ports + 1;
477 	xbar->num_sources = isi->pdata->num_ports;
478 	num_pads = xbar->num_sinks + xbar->num_sources;
479 
480 	xbar->pads = kcalloc(num_pads, sizeof(*xbar->pads), GFP_KERNEL);
481 	if (!xbar->pads)
482 		return -ENOMEM;
483 
484 	xbar->inputs = kcalloc(xbar->num_sinks, sizeof(*xbar->inputs),
485 			       GFP_KERNEL);
486 	if (!xbar->pads) {
487 		ret = -ENOMEM;
488 		goto err_free;
489 	}
490 
491 	for (i = 0; i < xbar->num_sinks; ++i)
492 		xbar->pads[i].flags = MEDIA_PAD_FL_SINK;
493 	for (i = 0; i < xbar->num_sources; ++i)
494 		xbar->pads[i + xbar->num_sinks].flags = MEDIA_PAD_FL_SOURCE;
495 
496 	ret = media_entity_pads_init(&sd->entity, num_pads, xbar->pads);
497 	if (ret)
498 		goto err_free;
499 
500 	ret = v4l2_subdev_init_finalize(sd);
501 	if (ret < 0)
502 		goto err_entity;
503 
504 	return 0;
505 
506 err_entity:
507 	media_entity_cleanup(&sd->entity);
508 err_free:
509 	kfree(xbar->pads);
510 	kfree(xbar->inputs);
511 
512 	return ret;
513 }
514 
515 void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar)
516 {
517 	media_entity_cleanup(&xbar->sd.entity);
518 	kfree(xbar->pads);
519 	kfree(xbar->inputs);
520 }
521 
522 int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar)
523 {
524 	return v4l2_device_register_subdev(&xbar->isi->v4l2_dev, &xbar->sd);
525 }
526 
527 void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar)
528 {
529 }
530