510 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * i.MX8 ISI - Input crossbar switch4 *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/v4l2-subdev.h>19 20#include "imx8-isi-core.h"21 22static inline struct mxc_isi_crossbar *to_isi_crossbar(struct v4l2_subdev *sd)23{24 return container_of(sd, struct mxc_isi_crossbar, sd);25}26 27static 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)31{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;36 int ret;37 38 if (!gasket_ops)39 return 0;40 41 /*42 * Configure and enable the gasket with the frame size and CSI-2 data43 * type. For YUV422 8-bit, enable dual component mode unconditionally,44 * to match the configuration of the CSIS.45 */46 47 ret = v4l2_subdev_call(remote_sd, pad, get_frame_desc, remote_pad, &fd);48 if (ret) {49 dev_err(isi->dev,50 "failed to get frame descriptor from '%s':%u: %d\n",51 remote_sd->name, remote_pad, ret);52 return ret;53 }54 55 if (fd.num_entries != 1) {56 dev_err(isi->dev, "invalid frame descriptor for '%s':%u\n",57 remote_sd->name, remote_pad);58 return -EINVAL;59 }60 61 fmt = v4l2_subdev_state_get_format(state, port, 0);62 if (!fmt)63 return -EINVAL;64 65 gasket_ops->enable(isi, &fd, fmt, port);66 return 0;67}68 69static void mxc_isi_crossbar_gasket_disable(struct mxc_isi_crossbar *xbar,70 unsigned int port)71{72 struct mxc_isi_dev *isi = xbar->isi;73 const struct mxc_gasket_ops *gasket_ops = isi->pdata->gasket_ops;74 75 if (!gasket_ops)76 return;77 78 gasket_ops->disable(isi, port);79}80 81/* -----------------------------------------------------------------------------82 * V4L2 subdev operations83 */84 85static 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,94};95 96static int __mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,97 struct v4l2_subdev_state *state,98 struct v4l2_subdev_krouting *routing)99{100 struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);101 struct v4l2_subdev_route *route;102 int ret;103 104 ret = v4l2_subdev_routing_validate(sd, routing,105 V4L2_SUBDEV_ROUTING_NO_N_TO_1);106 if (ret)107 return ret;108 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",115 route->sink_pad,116 route->source_pad - xbar->num_sinks);117 return -EINVAL;118 }119 }120 121 return v4l2_subdev_set_routing_with_fmt(sd, state, routing,122 &mxc_isi_crossbar_default_format);123}124 125static struct v4l2_subdev *126mxc_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,130 u32 *remote_pad)131{132 struct v4l2_subdev_route *route;133 struct v4l2_subdev *sd;134 struct media_pad *pad;135 u64 sink_streams = 0;136 int sink_pad = -1;137 138 /*139 * Translate the source pad and streams to the sink side. The routing140 * validation forbids stream merging, so all matching entries in the141 * routing table are guaranteed to have the same sink pad.142 *143 * TODO: This is likely worth a helper function, it could perhaps be144 * supported by v4l2_subdev_state_xlate_streams() with pad1 set to -1.145 */146 for_each_active_route(&state->routing, route) {147 if (route->source_pad != source_pad ||148 !(source_streams & BIT(route->source_stream)))149 continue;150 151 sink_streams |= BIT(route->sink_stream);152 sink_pad = route->sink_pad;153 }154 155 if (sink_pad < 0) {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);160 }161 162 pad = media_pad_remote_pad_first(&xbar->pads[sink_pad]);163 sd = media_entity_to_v4l2_subdev(pad->entity);164 if (!sd) {165 dev_dbg(xbar->isi->dev,166 "no entity connected to crossbar input %u\n",167 sink_pad);168 return ERR_PTR(-EPIPE);169 }170 171 *__sink_pad = sink_pad;172 *__sink_streams = sink_streams;173 *remote_pad = pad->index;174 175 return sd;176}177 178static int mxc_isi_crossbar_init_state(struct v4l2_subdev *sd,179 struct v4l2_subdev_state *state)180{181 struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);182 struct v4l2_subdev_krouting routing = { };183 struct v4l2_subdev_route *routes;184 unsigned int i;185 int ret;186 187 /*188 * Create a 1:1 mapping between pixel link inputs and outputs to189 * pipelines by default.190 */191 routes = kcalloc(xbar->num_sources, sizeof(*routes), GFP_KERNEL);192 if (!routes)193 return -ENOMEM;194 195 for (i = 0; i < xbar->num_sources; ++i) {196 struct v4l2_subdev_route *route = &routes[i];197 198 route->sink_pad = i;199 route->source_pad = i + xbar->num_sinks;200 route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;201 }202 203 routing.num_routes = xbar->num_sources;204 routing.routes = routes;205 206 ret = __mxc_isi_crossbar_set_routing(sd, state, &routing);207 208 kfree(routes);209 210 return ret;211}212 213static 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)216{217 struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);218 const struct mxc_isi_bus_format_info *info;219 220 if (code->pad >= xbar->num_sinks) {221 const struct v4l2_mbus_framefmt *format;222 223 /*224 * The media bus code on source pads is identical to the225 * connected sink pad.226 */227 if (code->index > 0)228 return -EINVAL;229 230 format = v4l2_subdev_state_get_opposite_stream_format(state,231 code->pad,232 code->stream);233 if (!format)234 return -EINVAL;235 236 code->code = format->code;237 238 return 0;239 }240 241 info = mxc_isi_bus_format_by_index(code->index, MXC_ISI_PIPE_PAD_SINK);242 if (!info)243 return -EINVAL;244 245 code->code = info->mbus_code;246 247 return 0;248}249 250static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd,251 struct v4l2_subdev_state *state,252 struct v4l2_subdev_format *fmt)253{254 struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);255 struct v4l2_mbus_framefmt *sink_fmt;256 struct v4l2_subdev_route *route;257 258 if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE &&259 media_pad_is_streaming(&xbar->pads[fmt->pad]))260 return -EBUSY;261 262 /*263 * The source pad format is always identical to the sink pad format and264 * can't be modified.265 */266 if (fmt->pad >= xbar->num_sinks)267 return v4l2_subdev_get_fmt(sd, state, fmt);268 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;272 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;278 279 /*280 * Set the format on the sink stream and propagate it to the source281 * streams.282 */283 sink_fmt = v4l2_subdev_state_get_format(state, fmt->pad, fmt->stream);284 if (!sink_fmt)285 return -EINVAL;286 287 *sink_fmt = fmt->format;288 289 /* TODO: A format propagation helper would be useful. */290 for_each_active_route(&state->routing, route) {291 struct v4l2_mbus_framefmt *source_fmt;292 293 if (route->sink_pad != fmt->pad ||294 route->sink_stream != fmt->stream)295 continue;296 297 source_fmt = v4l2_subdev_state_get_format(state,298 route->source_pad,299 route->source_stream);300 if (!source_fmt)301 return -EINVAL;302 303 *source_fmt = fmt->format;304 }305 306 return 0;307}308 309static 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)313{314 if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&315 media_entity_is_streaming(&sd->entity))316 return -EBUSY;317 318 return __mxc_isi_crossbar_set_routing(sd, state, routing);319}320 321static int mxc_isi_crossbar_enable_streams(struct v4l2_subdev *sd,322 struct v4l2_subdev_state *state,323 u32 pad, u64 streams_mask)324{325 struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);326 struct v4l2_subdev *remote_sd;327 struct mxc_isi_input *input;328 u64 sink_streams;329 u32 sink_pad;330 u32 remote_pad;331 int ret;332 333 remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,334 &sink_pad, &sink_streams,335 &remote_pad);336 if (IS_ERR(remote_sd))337 return PTR_ERR(remote_sd);338 339 input = &xbar->inputs[sink_pad];340 341 /*342 * TODO: Track per-stream enable counts to support multiplexed343 * streams.344 */345 if (!input->enable_count) {346 ret = mxc_isi_crossbar_gasket_enable(xbar, state, remote_sd,347 remote_pad, sink_pad);348 if (ret)349 return ret;350 351 ret = v4l2_subdev_enable_streams(remote_sd, remote_pad,352 sink_streams);353 if (ret) {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,357 remote_pad, ret);358 mxc_isi_crossbar_gasket_disable(xbar, sink_pad);359 return ret;360 }361 }362 363 input->enable_count++;364 365 return 0;366}367 368static int mxc_isi_crossbar_disable_streams(struct v4l2_subdev *sd,369 struct v4l2_subdev_state *state,370 u32 pad, u64 streams_mask)371{372 struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);373 struct v4l2_subdev *remote_sd;374 struct mxc_isi_input *input;375 u64 sink_streams;376 u32 sink_pad;377 u32 remote_pad;378 int ret = 0;379 380 remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,381 &sink_pad, &sink_streams,382 &remote_pad);383 if (IS_ERR(remote_sd))384 return PTR_ERR(remote_sd);385 386 input = &xbar->inputs[sink_pad];387 388 input->enable_count--;389 390 if (!input->enable_count) {391 ret = v4l2_subdev_disable_streams(remote_sd, remote_pad,392 sink_streams);393 if (ret)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,397 remote_pad, ret);398 399 mxc_isi_crossbar_gasket_disable(xbar, sink_pad);400 }401 402 return ret;403}404 405static 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,412};413 414static const struct v4l2_subdev_ops mxc_isi_crossbar_subdev_ops = {415 .pad = &mxc_isi_crossbar_subdev_pad_ops,416};417 418static const struct v4l2_subdev_internal_ops mxc_isi_crossbar_internal_ops = {419 .init_state = mxc_isi_crossbar_init_state,420};421 422static 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,426};427 428/* -----------------------------------------------------------------------------429 * Init & cleanup430 */431 432int mxc_isi_crossbar_init(struct mxc_isi_dev *isi)433{434 struct mxc_isi_crossbar *xbar = &isi->crossbar;435 struct v4l2_subdev *sd = &xbar->sd;436 unsigned int num_pads;437 unsigned int i;438 int ret;439 440 xbar->isi = isi;441 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));446 sd->dev = isi->dev;447 448 sd->entity.function = MEDIA_ENT_F_VID_MUX;449 sd->entity.ops = &mxc_isi_cross_entity_ops;450 451 /*452 * The subdev has one sink and one source per port, plus one sink for453 * the memory input.454 */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;458 459 xbar->pads = kcalloc(num_pads, sizeof(*xbar->pads), GFP_KERNEL);460 if (!xbar->pads)461 return -ENOMEM;462 463 xbar->inputs = kcalloc(xbar->num_sinks, sizeof(*xbar->inputs),464 GFP_KERNEL);465 if (!xbar->inputs) {466 ret = -ENOMEM;467 goto err_free;468 }469 470 for (i = 0; i < xbar->num_sinks; ++i)471 xbar->pads[i].flags = MEDIA_PAD_FL_SINK472 | 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;475 476 ret = media_entity_pads_init(&sd->entity, num_pads, xbar->pads);477 if (ret)478 goto err_free;479 480 ret = v4l2_subdev_init_finalize(sd);481 if (ret < 0)482 goto err_entity;483 484 return 0;485 486err_entity:487 media_entity_cleanup(&sd->entity);488err_free:489 kfree(xbar->pads);490 kfree(xbar->inputs);491 492 return ret;493}494 495void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar)496{497 media_entity_cleanup(&xbar->sd.entity);498 kfree(xbar->pads);499 kfree(xbar->inputs);500}501 502int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar)503{504 return v4l2_device_register_subdev(&xbar->isi->v4l2_dev, &xbar->sd);505}506 507void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar)508{509}510