1 // SPDX-License-Identifier: GPL-2.0-only
3 * i.MX8 ISI - Input crossbar switch
5 * Copyright (c) 2022 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
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>
17 #include <media/media-entity.h>
18 #include <media/v4l2-subdev.h>
20 #include "imx8-isi-core.h"
22 static inline struct mxc_isi_crossbar
*to_isi_crossbar(struct v4l2_subdev
*sd
)
24 return container_of(sd
, struct mxc_isi_crossbar
, sd
);
27 static int mxc_isi_crossbar_gasket_enable(struct mxc_isi_crossbar
*xbar
,
28 struct v4l2_subdev_state
*state
,
29 struct v4l2_subdev
*remote_sd
,
30 u32 remote_pad
, unsigned int port
)
32 struct mxc_isi_dev
*isi
= xbar
->isi
;
33 const struct mxc_gasket_ops
*gasket_ops
= isi
->pdata
->gasket_ops
;
34 const struct v4l2_mbus_framefmt
*fmt
;
35 struct v4l2_mbus_frame_desc fd
;
42 * Configure and enable the gasket with the frame size and CSI-2 data
43 * type. For YUV422 8-bit, enable dual component mode unconditionally,
44 * to match the configuration of the CSIS.
47 ret
= v4l2_subdev_call(remote_sd
, pad
, get_frame_desc
, remote_pad
, &fd
);
50 "failed to get frame descriptor from '%s':%u: %d\n",
51 remote_sd
->name
, remote_pad
, ret
);
55 if (fd
.num_entries
!= 1) {
56 dev_err(isi
->dev
, "invalid frame descriptor for '%s':%u\n",
57 remote_sd
->name
, remote_pad
);
61 fmt
= v4l2_subdev_state_get_format(state
, port
, 0);
65 gasket_ops
->enable(isi
, &fd
, fmt
, port
);
69 static void mxc_isi_crossbar_gasket_disable(struct mxc_isi_crossbar
*xbar
,
72 struct mxc_isi_dev
*isi
= xbar
->isi
;
73 const struct mxc_gasket_ops
*gasket_ops
= isi
->pdata
->gasket_ops
;
78 gasket_ops
->disable(isi
, port
);
81 /* -----------------------------------------------------------------------------
82 * V4L2 subdev operations
85 static const struct v4l2_mbus_framefmt mxc_isi_crossbar_default_format
= {
86 .code
= MXC_ISI_DEF_MBUS_CODE_SINK
,
87 .width
= MXC_ISI_DEF_WIDTH
,
88 .height
= MXC_ISI_DEF_HEIGHT
,
89 .field
= V4L2_FIELD_NONE
,
90 .colorspace
= MXC_ISI_DEF_COLOR_SPACE
,
91 .ycbcr_enc
= MXC_ISI_DEF_YCBCR_ENC
,
92 .quantization
= MXC_ISI_DEF_QUANTIZATION
,
93 .xfer_func
= MXC_ISI_DEF_XFER_FUNC
,
96 static int __mxc_isi_crossbar_set_routing(struct v4l2_subdev
*sd
,
97 struct v4l2_subdev_state
*state
,
98 struct v4l2_subdev_krouting
*routing
)
100 struct mxc_isi_crossbar
*xbar
= to_isi_crossbar(sd
);
101 struct v4l2_subdev_route
*route
;
104 ret
= v4l2_subdev_routing_validate(sd
, routing
,
105 V4L2_SUBDEV_ROUTING_NO_N_TO_1
);
109 /* The memory input can be routed to the first pipeline only. */
110 for_each_active_route(&state
->routing
, route
) {
111 if (route
->sink_pad
== xbar
->num_sinks
- 1 &&
112 route
->source_pad
!= xbar
->num_sinks
) {
113 dev_dbg(xbar
->isi
->dev
,
114 "invalid route from memory input (%u) to pipe %u\n",
116 route
->source_pad
- xbar
->num_sinks
);
121 return v4l2_subdev_set_routing_with_fmt(sd
, state
, routing
,
122 &mxc_isi_crossbar_default_format
);
125 static struct v4l2_subdev
*
126 mxc_isi_crossbar_xlate_streams(struct mxc_isi_crossbar
*xbar
,
127 struct v4l2_subdev_state
*state
,
128 u32 source_pad
, u64 source_streams
,
129 u32
*__sink_pad
, u64
*__sink_streams
,
132 struct v4l2_subdev_route
*route
;
133 struct v4l2_subdev
*sd
;
134 struct media_pad
*pad
;
135 u64 sink_streams
= 0;
139 * Translate the source pad and streams to the sink side. The routing
140 * validation forbids stream merging, so all matching entries in the
141 * routing table are guaranteed to have the same sink pad.
143 * TODO: This is likely worth a helper function, it could perhaps be
144 * supported by v4l2_subdev_state_xlate_streams() with pad1 set to -1.
146 for_each_active_route(&state
->routing
, route
) {
147 if (route
->source_pad
!= source_pad
||
148 !(source_streams
& BIT(route
->source_stream
)))
151 sink_streams
|= BIT(route
->sink_stream
);
152 sink_pad
= route
->sink_pad
;
156 dev_dbg(xbar
->isi
->dev
,
157 "no stream connected to pipeline %u\n",
158 source_pad
- xbar
->num_sinks
);
159 return ERR_PTR(-EPIPE
);
162 pad
= media_pad_remote_pad_first(&xbar
->pads
[sink_pad
]);
163 sd
= media_entity_to_v4l2_subdev(pad
->entity
);
165 dev_dbg(xbar
->isi
->dev
,
166 "no entity connected to crossbar input %u\n",
168 return ERR_PTR(-EPIPE
);
171 *__sink_pad
= sink_pad
;
172 *__sink_streams
= sink_streams
;
173 *remote_pad
= pad
->index
;
178 static int mxc_isi_crossbar_init_state(struct v4l2_subdev
*sd
,
179 struct v4l2_subdev_state
*state
)
181 struct mxc_isi_crossbar
*xbar
= to_isi_crossbar(sd
);
182 struct v4l2_subdev_krouting routing
= { };
183 struct v4l2_subdev_route
*routes
;
188 * Create a 1:1 mapping between pixel link inputs and outputs to
189 * pipelines by default.
191 routes
= kcalloc(xbar
->num_sources
, sizeof(*routes
), GFP_KERNEL
);
195 for (i
= 0; i
< xbar
->num_sources
; ++i
) {
196 struct v4l2_subdev_route
*route
= &routes
[i
];
199 route
->source_pad
= i
+ xbar
->num_sinks
;
200 route
->flags
= V4L2_SUBDEV_ROUTE_FL_ACTIVE
;
203 routing
.num_routes
= xbar
->num_sources
;
204 routing
.routes
= routes
;
206 ret
= __mxc_isi_crossbar_set_routing(sd
, state
, &routing
);
213 static int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev
*sd
,
214 struct v4l2_subdev_state
*state
,
215 struct v4l2_subdev_mbus_code_enum
*code
)
217 struct mxc_isi_crossbar
*xbar
= to_isi_crossbar(sd
);
218 const struct mxc_isi_bus_format_info
*info
;
220 if (code
->pad
>= xbar
->num_sinks
) {
221 const struct v4l2_mbus_framefmt
*format
;
224 * The media bus code on source pads is identical to the
225 * connected sink pad.
230 format
= v4l2_subdev_state_get_opposite_stream_format(state
,
236 code
->code
= format
->code
;
241 info
= mxc_isi_bus_format_by_index(code
->index
, MXC_ISI_PIPE_PAD_SINK
);
245 code
->code
= info
->mbus_code
;
250 static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev
*sd
,
251 struct v4l2_subdev_state
*state
,
252 struct v4l2_subdev_format
*fmt
)
254 struct mxc_isi_crossbar
*xbar
= to_isi_crossbar(sd
);
255 struct v4l2_mbus_framefmt
*sink_fmt
;
256 struct v4l2_subdev_route
*route
;
258 if (fmt
->which
== V4L2_SUBDEV_FORMAT_ACTIVE
&&
259 media_pad_is_streaming(&xbar
->pads
[fmt
->pad
]))
263 * The source pad format is always identical to the sink pad format and
266 if (fmt
->pad
>= xbar
->num_sinks
)
267 return v4l2_subdev_get_fmt(sd
, state
, fmt
);
269 /* Validate the requested format. */
270 if (!mxc_isi_bus_format_by_code(fmt
->format
.code
, MXC_ISI_PIPE_PAD_SINK
))
271 fmt
->format
.code
= MXC_ISI_DEF_MBUS_CODE_SINK
;
273 fmt
->format
.width
= clamp_t(unsigned int, fmt
->format
.width
,
274 MXC_ISI_MIN_WIDTH
, MXC_ISI_MAX_WIDTH_CHAINED
);
275 fmt
->format
.height
= clamp_t(unsigned int, fmt
->format
.height
,
276 MXC_ISI_MIN_HEIGHT
, MXC_ISI_MAX_HEIGHT
);
277 fmt
->format
.field
= V4L2_FIELD_NONE
;
280 * Set the format on the sink stream and propagate it to the source
283 sink_fmt
= v4l2_subdev_state_get_format(state
, fmt
->pad
, fmt
->stream
);
287 *sink_fmt
= fmt
->format
;
289 /* TODO: A format propagation helper would be useful. */
290 for_each_active_route(&state
->routing
, route
) {
291 struct v4l2_mbus_framefmt
*source_fmt
;
293 if (route
->sink_pad
!= fmt
->pad
||
294 route
->sink_stream
!= fmt
->stream
)
297 source_fmt
= v4l2_subdev_state_get_format(state
,
299 route
->source_stream
);
303 *source_fmt
= fmt
->format
;
309 static int mxc_isi_crossbar_set_routing(struct v4l2_subdev
*sd
,
310 struct v4l2_subdev_state
*state
,
311 enum v4l2_subdev_format_whence which
,
312 struct v4l2_subdev_krouting
*routing
)
314 if (which
== V4L2_SUBDEV_FORMAT_ACTIVE
&&
315 media_entity_is_streaming(&sd
->entity
))
318 return __mxc_isi_crossbar_set_routing(sd
, state
, routing
);
321 static int mxc_isi_crossbar_enable_streams(struct v4l2_subdev
*sd
,
322 struct v4l2_subdev_state
*state
,
323 u32 pad
, u64 streams_mask
)
325 struct mxc_isi_crossbar
*xbar
= to_isi_crossbar(sd
);
326 struct v4l2_subdev
*remote_sd
;
327 struct mxc_isi_input
*input
;
333 remote_sd
= mxc_isi_crossbar_xlate_streams(xbar
, state
, pad
, streams_mask
,
334 &sink_pad
, &sink_streams
,
336 if (IS_ERR(remote_sd
))
337 return PTR_ERR(remote_sd
);
339 input
= &xbar
->inputs
[sink_pad
];
342 * TODO: Track per-stream enable counts to support multiplexed
345 if (!input
->enable_count
) {
346 ret
= mxc_isi_crossbar_gasket_enable(xbar
, state
, remote_sd
,
347 remote_pad
, sink_pad
);
351 ret
= v4l2_subdev_enable_streams(remote_sd
, remote_pad
,
354 dev_err(xbar
->isi
->dev
,
355 "failed to %s streams 0x%llx on '%s':%u: %d\n",
356 "enable", sink_streams
, remote_sd
->name
,
358 mxc_isi_crossbar_gasket_disable(xbar
, sink_pad
);
363 input
->enable_count
++;
368 static int mxc_isi_crossbar_disable_streams(struct v4l2_subdev
*sd
,
369 struct v4l2_subdev_state
*state
,
370 u32 pad
, u64 streams_mask
)
372 struct mxc_isi_crossbar
*xbar
= to_isi_crossbar(sd
);
373 struct v4l2_subdev
*remote_sd
;
374 struct mxc_isi_input
*input
;
380 remote_sd
= mxc_isi_crossbar_xlate_streams(xbar
, state
, pad
, streams_mask
,
381 &sink_pad
, &sink_streams
,
383 if (IS_ERR(remote_sd
))
384 return PTR_ERR(remote_sd
);
386 input
= &xbar
->inputs
[sink_pad
];
388 input
->enable_count
--;
390 if (!input
->enable_count
) {
391 ret
= v4l2_subdev_disable_streams(remote_sd
, remote_pad
,
394 dev_err(xbar
->isi
->dev
,
395 "failed to %s streams 0x%llx on '%s':%u: %d\n",
396 "disable", sink_streams
, remote_sd
->name
,
399 mxc_isi_crossbar_gasket_disable(xbar
, sink_pad
);
405 static const struct v4l2_subdev_pad_ops mxc_isi_crossbar_subdev_pad_ops
= {
406 .enum_mbus_code
= mxc_isi_crossbar_enum_mbus_code
,
407 .get_fmt
= v4l2_subdev_get_fmt
,
408 .set_fmt
= mxc_isi_crossbar_set_fmt
,
409 .set_routing
= mxc_isi_crossbar_set_routing
,
410 .enable_streams
= mxc_isi_crossbar_enable_streams
,
411 .disable_streams
= mxc_isi_crossbar_disable_streams
,
414 static const struct v4l2_subdev_ops mxc_isi_crossbar_subdev_ops
= {
415 .pad
= &mxc_isi_crossbar_subdev_pad_ops
,
418 static const struct v4l2_subdev_internal_ops mxc_isi_crossbar_internal_ops
= {
419 .init_state
= mxc_isi_crossbar_init_state
,
422 static const struct media_entity_operations mxc_isi_cross_entity_ops
= {
423 .get_fwnode_pad
= v4l2_subdev_get_fwnode_pad_1_to_1
,
424 .link_validate
= v4l2_subdev_link_validate
,
425 .has_pad_interdep
= v4l2_subdev_has_pad_interdep
,
428 /* -----------------------------------------------------------------------------
432 int mxc_isi_crossbar_init(struct mxc_isi_dev
*isi
)
434 struct mxc_isi_crossbar
*xbar
= &isi
->crossbar
;
435 struct v4l2_subdev
*sd
= &xbar
->sd
;
436 unsigned int num_pads
;
442 v4l2_subdev_init(sd
, &mxc_isi_crossbar_subdev_ops
);
443 sd
->internal_ops
= &mxc_isi_crossbar_internal_ops
;
444 sd
->flags
|= V4L2_SUBDEV_FL_HAS_DEVNODE
| V4L2_SUBDEV_FL_STREAMS
;
445 strscpy(sd
->name
, "crossbar", sizeof(sd
->name
));
448 sd
->entity
.function
= MEDIA_ENT_F_VID_MUX
;
449 sd
->entity
.ops
= &mxc_isi_cross_entity_ops
;
452 * The subdev has one sink and one source per port, plus one sink for
455 xbar
->num_sinks
= isi
->pdata
->num_ports
+ 1;
456 xbar
->num_sources
= isi
->pdata
->num_ports
;
457 num_pads
= xbar
->num_sinks
+ xbar
->num_sources
;
459 xbar
->pads
= kcalloc(num_pads
, sizeof(*xbar
->pads
), GFP_KERNEL
);
463 xbar
->inputs
= kcalloc(xbar
->num_sinks
, sizeof(*xbar
->inputs
),
470 for (i
= 0; i
< xbar
->num_sinks
; ++i
)
471 xbar
->pads
[i
].flags
= MEDIA_PAD_FL_SINK
472 | MEDIA_PAD_FL_MUST_CONNECT
;
473 for (i
= 0; i
< xbar
->num_sources
; ++i
)
474 xbar
->pads
[i
+ xbar
->num_sinks
].flags
= MEDIA_PAD_FL_SOURCE
;
476 ret
= media_entity_pads_init(&sd
->entity
, num_pads
, xbar
->pads
);
480 ret
= v4l2_subdev_init_finalize(sd
);
487 media_entity_cleanup(&sd
->entity
);
495 void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar
*xbar
)
497 media_entity_cleanup(&xbar
->sd
.entity
);
502 int mxc_isi_crossbar_register(struct mxc_isi_crossbar
*xbar
)
504 return v4l2_device_register_subdev(&xbar
->isi
->v4l2_dev
, &xbar
->sd
);
507 void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar
*xbar
)