873 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Copyright 2021-2022 Bootlin4 * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>5 */6 7#include <linux/pm_runtime.h>8#include <linux/regmap.h>9#include <media/v4l2-device.h>10#include <media/v4l2-fwnode.h>11 12#include "sun6i_csi.h"13#include "sun6i_csi_bridge.h"14#include "sun6i_csi_reg.h"15 16/* Helpers */17 18void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev,19 unsigned int *width, unsigned int *height)20{21 if (width)22 *width = csi_dev->bridge.mbus_format.width;23 if (height)24 *height = csi_dev->bridge.mbus_format.height;25}26 27void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev,28 u32 *mbus_code, u32 *field)29{30 if (mbus_code)31 *mbus_code = csi_dev->bridge.mbus_format.code;32 if (field)33 *field = csi_dev->bridge.mbus_format.field;34}35 36/* Format */37 38static const struct sun6i_csi_bridge_format sun6i_csi_bridge_formats[] = {39 /* Bayer */40 {41 .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,42 .input_format = SUN6I_CSI_INPUT_FMT_RAW,43 },44 {45 .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,46 .input_format = SUN6I_CSI_INPUT_FMT_RAW,47 },48 {49 .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,50 .input_format = SUN6I_CSI_INPUT_FMT_RAW,51 },52 {53 .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,54 .input_format = SUN6I_CSI_INPUT_FMT_RAW,55 },56 {57 .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,58 .input_format = SUN6I_CSI_INPUT_FMT_RAW,59 },60 {61 .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,62 .input_format = SUN6I_CSI_INPUT_FMT_RAW,63 },64 {65 .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,66 .input_format = SUN6I_CSI_INPUT_FMT_RAW,67 },68 {69 .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,70 .input_format = SUN6I_CSI_INPUT_FMT_RAW,71 },72 {73 .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,74 .input_format = SUN6I_CSI_INPUT_FMT_RAW,75 },76 {77 .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,78 .input_format = SUN6I_CSI_INPUT_FMT_RAW,79 },80 {81 .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,82 .input_format = SUN6I_CSI_INPUT_FMT_RAW,83 },84 {85 .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,86 .input_format = SUN6I_CSI_INPUT_FMT_RAW,87 },88 /* RGB */89 {90 .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,91 .input_format = SUN6I_CSI_INPUT_FMT_RAW,92 },93 {94 .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE,95 .input_format = SUN6I_CSI_INPUT_FMT_RAW,96 },97 /* YUV422 */98 {99 .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,100 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,101 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV,102 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU,103 },104 {105 .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,106 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,107 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY,108 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY,109 },110 {111 .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,112 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,113 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU,114 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV,115 },116 {117 .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,118 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,119 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY,120 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY,121 },122 {123 .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,124 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,125 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY,126 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY,127 },128 {129 .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,130 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,131 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV,132 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU,133 },134 {135 .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,136 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,137 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY,138 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY,139 },140 {141 .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,142 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,143 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU,144 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV,145 },146 {147 .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,148 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,149 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY,150 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY,151 },152 {153 .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,154 .input_format = SUN6I_CSI_INPUT_FMT_YUV422,155 .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY,156 .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY,157 },158 /* Compressed */159 {160 .mbus_code = MEDIA_BUS_FMT_JPEG_1X8,161 .input_format = SUN6I_CSI_INPUT_FMT_RAW,162 },163};164 165const struct sun6i_csi_bridge_format *166sun6i_csi_bridge_format_find(u32 mbus_code)167{168 unsigned int i;169 170 for (i = 0; i < ARRAY_SIZE(sun6i_csi_bridge_formats); i++)171 if (sun6i_csi_bridge_formats[i].mbus_code == mbus_code)172 return &sun6i_csi_bridge_formats[i];173 174 return NULL;175}176 177/* Bridge */178 179static void sun6i_csi_bridge_irq_enable(struct sun6i_csi_device *csi_dev)180{181 struct regmap *regmap = csi_dev->regmap;182 183 regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG,184 SUN6I_CSI_CH_INT_EN_VS |185 SUN6I_CSI_CH_INT_EN_HB_OF |186 SUN6I_CSI_CH_INT_EN_FIFO2_OF |187 SUN6I_CSI_CH_INT_EN_FIFO1_OF |188 SUN6I_CSI_CH_INT_EN_FIFO0_OF |189 SUN6I_CSI_CH_INT_EN_FD |190 SUN6I_CSI_CH_INT_EN_CD);191}192 193static void sun6i_csi_bridge_irq_disable(struct sun6i_csi_device *csi_dev)194{195 struct regmap *regmap = csi_dev->regmap;196 197 regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0);198}199 200static void sun6i_csi_bridge_irq_clear(struct sun6i_csi_device *csi_dev)201{202 struct regmap *regmap = csi_dev->regmap;203 204 regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0);205 regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG,206 SUN6I_CSI_CH_INT_STA_CLEAR);207}208 209static void sun6i_csi_bridge_enable(struct sun6i_csi_device *csi_dev)210{211 struct regmap *regmap = csi_dev->regmap;212 213 regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN,214 SUN6I_CSI_EN_CSI_EN);215 216 regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON,217 SUN6I_CSI_CAP_VCAP_ON);218}219 220static void sun6i_csi_bridge_disable(struct sun6i_csi_device *csi_dev)221{222 struct regmap *regmap = csi_dev->regmap;223 224 regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON, 0);225 regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN, 0);226}227 228static void229sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev)230{231 struct device *dev = csi_dev->dev;232 struct regmap *regmap = csi_dev->regmap;233 struct v4l2_fwnode_endpoint *endpoint =234 &csi_dev->bridge.source_parallel.endpoint;235 unsigned char bus_width = endpoint->bus.parallel.bus_width;236 unsigned int flags = endpoint->bus.parallel.flags;237 u32 field;238 u32 value = SUN6I_CSI_IF_CFG_IF_CSI;239 240 sun6i_csi_bridge_format(csi_dev, NULL, &field);241 242 if (field == V4L2_FIELD_INTERLACED ||243 field == V4L2_FIELD_INTERLACED_TB ||244 field == V4L2_FIELD_INTERLACED_BT)245 value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED |246 SUN6I_CSI_IF_CFG_FIELD_DT_PCLK_SHIFT(1) |247 SUN6I_CSI_IF_CFG_FIELD_DT_FIELD_VSYNC;248 else249 value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE;250 251 switch (endpoint->bus_type) {252 case V4L2_MBUS_PARALLEL:253 if (bus_width == 16)254 value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_COMBINED;255 else256 value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_RAW;257 258 if (flags & V4L2_MBUS_FIELD_EVEN_LOW)259 value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE;260 else261 value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE;262 263 if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)264 value |= SUN6I_CSI_IF_CFG_VREF_POL_NEGATIVE;265 else266 value |= SUN6I_CSI_IF_CFG_VREF_POL_POSITIVE;267 268 if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)269 value |= SUN6I_CSI_IF_CFG_HREF_POL_NEGATIVE;270 else271 value |= SUN6I_CSI_IF_CFG_HREF_POL_POSITIVE;272 273 if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING)274 value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING;275 else276 value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING;277 break;278 case V4L2_MBUS_BT656:279 if (bus_width == 16)280 value |= SUN6I_CSI_IF_CFG_IF_CSI_BT1120;281 else282 value |= SUN6I_CSI_IF_CFG_IF_CSI_BT656;283 284 if (flags & V4L2_MBUS_FIELD_EVEN_LOW)285 value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE;286 else287 value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE;288 289 if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)290 value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING;291 else292 value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING;293 break;294 default:295 dev_warn(dev, "unsupported bus type: %d\n", endpoint->bus_type);296 break;297 }298 299 switch (bus_width) {300 case 8:301 /* 16-bit YUV formats use a doubled width in 8-bit mode. */302 case 16:303 value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_8;304 break;305 case 10:306 value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_10;307 break;308 case 12:309 value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_12;310 break;311 default:312 dev_warn(dev, "unsupported bus width: %u\n", bus_width);313 break;314 }315 316 regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value);317}318 319static void320sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev)321{322 struct regmap *regmap = csi_dev->regmap;323 u32 value = SUN6I_CSI_IF_CFG_IF_MIPI;324 u32 field;325 326 sun6i_csi_bridge_format(csi_dev, NULL, &field);327 328 if (field == V4L2_FIELD_INTERLACED ||329 field == V4L2_FIELD_INTERLACED_TB ||330 field == V4L2_FIELD_INTERLACED_BT)331 value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED;332 else333 value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE;334 335 regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value);336}337 338static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev)339{340 struct regmap *regmap = csi_dev->regmap;341 bool capture_streaming = csi_dev->capture.state.streaming;342 const struct sun6i_csi_bridge_format *bridge_format;343 const struct sun6i_csi_capture_format *capture_format;344 u32 mbus_code, field, pixelformat;345 u8 input_format, input_yuv_seq, output_format;346 u32 value = 0;347 348 sun6i_csi_bridge_format(csi_dev, &mbus_code, &field);349 350 bridge_format = sun6i_csi_bridge_format_find(mbus_code);351 if (WARN_ON(!bridge_format))352 return;353 354 input_format = bridge_format->input_format;355 input_yuv_seq = bridge_format->input_yuv_seq;356 357 if (capture_streaming) {358 sun6i_csi_capture_format(csi_dev, &pixelformat, NULL);359 360 capture_format = sun6i_csi_capture_format_find(pixelformat);361 if (WARN_ON(!capture_format))362 return;363 364 if (capture_format->input_format_raw)365 input_format = SUN6I_CSI_INPUT_FMT_RAW;366 367 if (capture_format->input_yuv_seq_invert)368 input_yuv_seq = bridge_format->input_yuv_seq_invert;369 370 if (field == V4L2_FIELD_INTERLACED ||371 field == V4L2_FIELD_INTERLACED_TB ||372 field == V4L2_FIELD_INTERLACED_BT)373 output_format = capture_format->output_format_field;374 else375 output_format = capture_format->output_format_frame;376 377 value |= SUN6I_CSI_CH_CFG_OUTPUT_FMT(output_format);378 }379 380 value |= SUN6I_CSI_CH_CFG_INPUT_FMT(input_format);381 value |= SUN6I_CSI_CH_CFG_INPUT_YUV_SEQ(input_yuv_seq);382 383 if (field == V4L2_FIELD_TOP)384 value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD0;385 else if (field == V4L2_FIELD_BOTTOM)386 value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD1;387 else388 value |= SUN6I_CSI_CH_CFG_FIELD_SEL_EITHER;389 390 regmap_write(regmap, SUN6I_CSI_CH_CFG_REG, value);391}392 393static void sun6i_csi_bridge_configure(struct sun6i_csi_device *csi_dev,394 struct sun6i_csi_bridge_source *source)395{396 struct sun6i_csi_bridge *bridge = &csi_dev->bridge;397 398 if (source == &bridge->source_parallel)399 sun6i_csi_bridge_configure_parallel(csi_dev);400 else401 sun6i_csi_bridge_configure_mipi_csi2(csi_dev);402 403 sun6i_csi_bridge_configure_format(csi_dev);404}405 406/* V4L2 Subdev */407 408static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on)409{410 struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);411 struct sun6i_csi_bridge *bridge = &csi_dev->bridge;412 struct media_pad *local_pad = &bridge->pads[SUN6I_CSI_BRIDGE_PAD_SINK];413 bool capture_streaming = csi_dev->capture.state.streaming;414 struct device *dev = csi_dev->dev;415 struct sun6i_csi_bridge_source *source;416 struct v4l2_subdev *source_subdev;417 struct media_pad *remote_pad;418 int ret;419 420 /* Source */421 422 remote_pad = media_pad_remote_pad_unique(local_pad);423 if (IS_ERR(remote_pad)) {424 dev_err(dev,425 "zero or more than a single source connected to the bridge\n");426 return PTR_ERR(remote_pad);427 }428 429 source_subdev = media_entity_to_v4l2_subdev(remote_pad->entity);430 431 if (source_subdev == bridge->source_parallel.subdev)432 source = &bridge->source_parallel;433 else434 source = &bridge->source_mipi_csi2;435 436 if (!on) {437 v4l2_subdev_call(source_subdev, video, s_stream, 0);438 ret = 0;439 goto disable;440 }441 442 /* PM */443 444 ret = pm_runtime_resume_and_get(dev);445 if (ret < 0)446 return ret;447 448 /* Clear */449 450 sun6i_csi_bridge_irq_clear(csi_dev);451 452 /* Configure */453 454 sun6i_csi_bridge_configure(csi_dev, source);455 456 if (capture_streaming)457 sun6i_csi_capture_configure(csi_dev);458 459 /* State Update */460 461 if (capture_streaming)462 sun6i_csi_capture_state_update(csi_dev);463 464 /* Enable */465 466 if (capture_streaming)467 sun6i_csi_bridge_irq_enable(csi_dev);468 469 sun6i_csi_bridge_enable(csi_dev);470 471 ret = v4l2_subdev_call(source_subdev, video, s_stream, 1);472 if (ret && ret != -ENOIOCTLCMD)473 goto disable;474 475 return 0;476 477disable:478 if (capture_streaming)479 sun6i_csi_bridge_irq_disable(csi_dev);480 481 sun6i_csi_bridge_disable(csi_dev);482 483 pm_runtime_put(dev);484 485 return ret;486}487 488static const struct v4l2_subdev_video_ops sun6i_csi_bridge_video_ops = {489 .s_stream = sun6i_csi_bridge_s_stream,490};491 492static void493sun6i_csi_bridge_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format)494{495 if (!sun6i_csi_bridge_format_find(mbus_format->code))496 mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code;497 498 mbus_format->field = V4L2_FIELD_NONE;499 mbus_format->colorspace = V4L2_COLORSPACE_RAW;500 mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT;501 mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT;502}503 504static int sun6i_csi_bridge_init_state(struct v4l2_subdev *subdev,505 struct v4l2_subdev_state *state)506{507 struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);508 unsigned int pad = SUN6I_CSI_BRIDGE_PAD_SINK;509 struct v4l2_mbus_framefmt *mbus_format =510 v4l2_subdev_state_get_format(state, pad);511 struct mutex *lock = &csi_dev->bridge.lock;512 513 mutex_lock(lock);514 515 mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code;516 mbus_format->width = 1280;517 mbus_format->height = 720;518 519 sun6i_csi_bridge_mbus_format_prepare(mbus_format);520 521 mutex_unlock(lock);522 523 return 0;524}525 526static int527sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev,528 struct v4l2_subdev_state *state,529 struct v4l2_subdev_mbus_code_enum *code_enum)530{531 if (code_enum->index >= ARRAY_SIZE(sun6i_csi_bridge_formats))532 return -EINVAL;533 534 code_enum->code = sun6i_csi_bridge_formats[code_enum->index].mbus_code;535 536 return 0;537}538 539static int sun6i_csi_bridge_get_fmt(struct v4l2_subdev *subdev,540 struct v4l2_subdev_state *state,541 struct v4l2_subdev_format *format)542{543 struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);544 struct v4l2_mbus_framefmt *mbus_format = &format->format;545 struct mutex *lock = &csi_dev->bridge.lock;546 547 mutex_lock(lock);548 549 if (format->which == V4L2_SUBDEV_FORMAT_TRY)550 *mbus_format = *v4l2_subdev_state_get_format(state,551 format->pad);552 else553 *mbus_format = csi_dev->bridge.mbus_format;554 555 mutex_unlock(lock);556 557 return 0;558}559 560static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev,561 struct v4l2_subdev_state *state,562 struct v4l2_subdev_format *format)563{564 struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev);565 struct v4l2_mbus_framefmt *mbus_format = &format->format;566 struct mutex *lock = &csi_dev->bridge.lock;567 568 mutex_lock(lock);569 570 sun6i_csi_bridge_mbus_format_prepare(mbus_format);571 572 if (format->which == V4L2_SUBDEV_FORMAT_TRY)573 *v4l2_subdev_state_get_format(state, format->pad) =574 *mbus_format;575 else576 csi_dev->bridge.mbus_format = *mbus_format;577 578 mutex_unlock(lock);579 580 return 0;581}582 583static const struct v4l2_subdev_pad_ops sun6i_csi_bridge_pad_ops = {584 .enum_mbus_code = sun6i_csi_bridge_enum_mbus_code,585 .get_fmt = sun6i_csi_bridge_get_fmt,586 .set_fmt = sun6i_csi_bridge_set_fmt,587};588 589static const struct v4l2_subdev_ops sun6i_csi_bridge_subdev_ops = {590 .video = &sun6i_csi_bridge_video_ops,591 .pad = &sun6i_csi_bridge_pad_ops,592};593 594static const struct v4l2_subdev_internal_ops sun6i_csi_bridge_internal_ops = {595 .init_state = sun6i_csi_bridge_init_state,596};597 598/* Media Entity */599 600static const struct media_entity_operations sun6i_csi_bridge_entity_ops = {601 .link_validate = v4l2_subdev_link_validate,602};603 604/* V4L2 Async */605 606static int sun6i_csi_bridge_link(struct sun6i_csi_device *csi_dev,607 int sink_pad_index,608 struct v4l2_subdev *remote_subdev,609 bool enabled)610{611 struct device *dev = csi_dev->dev;612 struct v4l2_subdev *subdev = &csi_dev->bridge.subdev;613 struct media_entity *sink_entity = &subdev->entity;614 struct media_entity *source_entity = &remote_subdev->entity;615 int source_pad_index;616 int ret;617 618 /* Get the first remote source pad. */619 ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode,620 MEDIA_PAD_FL_SOURCE);621 if (ret < 0) {622 dev_err(dev, "missing source pad in external entity %s\n",623 source_entity->name);624 return -EINVAL;625 }626 627 source_pad_index = ret;628 629 dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name,630 source_pad_index, sink_entity->name, sink_pad_index);631 632 ret = media_create_pad_link(source_entity, source_pad_index,633 sink_entity, sink_pad_index,634 enabled ? MEDIA_LNK_FL_ENABLED : 0);635 if (ret < 0) {636 dev_err(dev, "failed to create %s:%u -> %s:%u link\n",637 source_entity->name, source_pad_index,638 sink_entity->name, sink_pad_index);639 return ret;640 }641 642 return 0;643}644 645static int646sun6i_csi_bridge_notifier_bound(struct v4l2_async_notifier *notifier,647 struct v4l2_subdev *remote_subdev,648 struct v4l2_async_connection *async_subdev)649{650 struct sun6i_csi_device *csi_dev =651 container_of(notifier, struct sun6i_csi_device,652 bridge.notifier);653 struct sun6i_csi_bridge_async_subdev *bridge_async_subdev =654 container_of(async_subdev, struct sun6i_csi_bridge_async_subdev,655 async_subdev);656 struct sun6i_csi_bridge *bridge = &csi_dev->bridge;657 struct sun6i_csi_bridge_source *source = bridge_async_subdev->source;658 bool enabled = false;659 int ret;660 661 switch (source->endpoint.base.port) {662 case SUN6I_CSI_PORT_PARALLEL:663 enabled = true;664 break;665 case SUN6I_CSI_PORT_MIPI_CSI2:666 enabled = !bridge->source_parallel.expected;667 break;668 default:669 return -EINVAL;670 }671 672 source->subdev = remote_subdev;673 674 if (csi_dev->isp_available) {675 /*676 * Hook to the first available remote subdev to get v4l2 and677 * media devices and register the capture device then.678 */679 ret = sun6i_csi_isp_complete(csi_dev, remote_subdev->v4l2_dev);680 if (ret)681 return ret;682 }683 684 return sun6i_csi_bridge_link(csi_dev, SUN6I_CSI_BRIDGE_PAD_SINK,685 remote_subdev, enabled);686}687 688static int689sun6i_csi_bridge_notifier_complete(struct v4l2_async_notifier *notifier)690{691 struct sun6i_csi_device *csi_dev =692 container_of(notifier, struct sun6i_csi_device,693 bridge.notifier);694 struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev;695 696 if (csi_dev->isp_available)697 return 0;698 699 return v4l2_device_register_subdev_nodes(v4l2_dev);700}701 702static const struct v4l2_async_notifier_operations703sun6i_csi_bridge_notifier_ops = {704 .bound = sun6i_csi_bridge_notifier_bound,705 .complete = sun6i_csi_bridge_notifier_complete,706};707 708/* Bridge */709 710static int sun6i_csi_bridge_source_setup(struct sun6i_csi_device *csi_dev,711 struct sun6i_csi_bridge_source *source,712 u32 port,713 enum v4l2_mbus_type *bus_types)714{715 struct device *dev = csi_dev->dev;716 struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier;717 struct v4l2_fwnode_endpoint *endpoint = &source->endpoint;718 struct sun6i_csi_bridge_async_subdev *bridge_async_subdev;719 struct fwnode_handle *handle;720 int ret;721 722 handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), port, 0, 0);723 if (!handle)724 return -ENODEV;725 726 ret = v4l2_fwnode_endpoint_parse(handle, endpoint);727 if (ret)728 goto complete;729 730 if (bus_types) {731 bool valid = false;732 unsigned int i;733 734 for (i = 0; bus_types[i] != V4L2_MBUS_INVALID; i++) {735 if (endpoint->bus_type == bus_types[i]) {736 valid = true;737 break;738 }739 }740 741 if (!valid) {742 dev_err(dev, "unsupported bus type for port %d\n",743 port);744 ret = -EINVAL;745 goto complete;746 }747 }748 749 bridge_async_subdev =750 v4l2_async_nf_add_fwnode_remote(notifier, handle,751 struct752 sun6i_csi_bridge_async_subdev);753 if (IS_ERR(bridge_async_subdev)) {754 ret = PTR_ERR(bridge_async_subdev);755 goto complete;756 }757 758 bridge_async_subdev->source = source;759 760 source->expected = true;761 762complete:763 fwnode_handle_put(handle);764 765 return ret;766}767 768int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev)769{770 struct device *dev = csi_dev->dev;771 struct sun6i_csi_bridge *bridge = &csi_dev->bridge;772 struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev;773 struct v4l2_subdev *subdev = &bridge->subdev;774 struct v4l2_async_notifier *notifier = &bridge->notifier;775 struct media_pad *pads = bridge->pads;776 enum v4l2_mbus_type parallel_mbus_types[] = {777 V4L2_MBUS_PARALLEL,778 V4L2_MBUS_BT656,779 V4L2_MBUS_INVALID780 };781 int ret;782 783 mutex_init(&bridge->lock);784 785 /* V4L2 Subdev */786 787 v4l2_subdev_init(subdev, &sun6i_csi_bridge_subdev_ops);788 subdev->internal_ops = &sun6i_csi_bridge_internal_ops;789 strscpy(subdev->name, SUN6I_CSI_BRIDGE_NAME, sizeof(subdev->name));790 subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;791 subdev->owner = THIS_MODULE;792 subdev->dev = dev;793 794 v4l2_set_subdevdata(subdev, csi_dev);795 796 /* Media Entity */797 798 subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;799 subdev->entity.ops = &sun6i_csi_bridge_entity_ops;800 801 /* Media Pads */802 803 pads[SUN6I_CSI_BRIDGE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;804 pads[SUN6I_CSI_BRIDGE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE |805 MEDIA_PAD_FL_MUST_CONNECT;806 807 ret = media_entity_pads_init(&subdev->entity,808 SUN6I_CSI_BRIDGE_PAD_COUNT, pads);809 if (ret < 0)810 return ret;811 812 /* V4L2 Subdev */813 814 if (csi_dev->isp_available)815 ret = v4l2_async_register_subdev(subdev);816 else817 ret = v4l2_device_register_subdev(v4l2_dev, subdev);818 819 if (ret) {820 dev_err(dev, "failed to register v4l2 subdev: %d\n", ret);821 goto error_media_entity;822 }823 824 /* V4L2 Async */825 826 if (csi_dev->isp_available)827 v4l2_async_subdev_nf_init(notifier, subdev);828 else829 v4l2_async_nf_init(notifier, v4l2_dev);830 notifier->ops = &sun6i_csi_bridge_notifier_ops;831 832 sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_parallel,833 SUN6I_CSI_PORT_PARALLEL,834 parallel_mbus_types);835 sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_mipi_csi2,836 SUN6I_CSI_PORT_MIPI_CSI2, NULL);837 838 ret = v4l2_async_nf_register(notifier);839 if (ret) {840 dev_err(dev, "failed to register v4l2 async notifier: %d\n",841 ret);842 goto error_v4l2_async_notifier;843 }844 845 return 0;846 847error_v4l2_async_notifier:848 v4l2_async_nf_cleanup(notifier);849 850 if (csi_dev->isp_available)851 v4l2_async_unregister_subdev(subdev);852 else853 v4l2_device_unregister_subdev(subdev);854 855error_media_entity:856 media_entity_cleanup(&subdev->entity);857 858 return ret;859}860 861void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev)862{863 struct v4l2_subdev *subdev = &csi_dev->bridge.subdev;864 struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier;865 866 v4l2_async_nf_unregister(notifier);867 v4l2_async_nf_cleanup(notifier);868 869 v4l2_device_unregister_subdev(subdev);870 871 media_entity_cleanup(&subdev->entity);872}873