// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2014 - 2024 Intel Corporation

#include <linux/device.h>
#include <linux/module.h>

#include <media/ipu-isys.h>
#include <media/media-entity.h>
#include <media/v4l2-device.h>

#include "ipu.h"
#include "ipu-bus.h"
#include "ipu-isys.h"
#include "ipu-isys-csi2-be.h"
#include "ipu-isys-subdev.h"
#include "ipu-isys-video.h"

/*
 * Raw bayer format pixel order MUST BE MAINTAINED in groups of four codes.
 * Otherwise pixel order calculation below WILL BREAK!
 */
static const u32 csi2_be_supported_codes_pad[] = {
	MEDIA_BUS_FMT_SBGGR12_1X12,
	MEDIA_BUS_FMT_SGBRG12_1X12,
	MEDIA_BUS_FMT_SGRBG12_1X12,
	MEDIA_BUS_FMT_SRGGB12_1X12,
	MEDIA_BUS_FMT_SBGGR10_1X10,
	MEDIA_BUS_FMT_SGBRG10_1X10,
	MEDIA_BUS_FMT_SGRBG10_1X10,
	MEDIA_BUS_FMT_SRGGB10_1X10,
	MEDIA_BUS_FMT_SBGGR8_1X8,
	MEDIA_BUS_FMT_SGBRG8_1X8,
	MEDIA_BUS_FMT_SGRBG8_1X8,
	MEDIA_BUS_FMT_SRGGB8_1X8,
	0,
};

static const u32 *csi2_be_supported_codes[] = {
	csi2_be_supported_codes_pad,
	csi2_be_supported_codes_pad,
};

static struct v4l2_subdev_internal_ops csi2_be_sd_internal_ops = {
	.open = ipu_isys_subdev_open,
	.close = ipu_isys_subdev_close,
};

static const struct v4l2_subdev_core_ops csi2_be_sd_core_ops = {
};

static const struct v4l2_ctrl_config compression_ctrl_cfg = {
	.ops = NULL,
	.id = V4L2_CID_IPU_ISYS_COMPRESSION,
	.name = "ISYS CSI-BE compression",
	.type = V4L2_CTRL_TYPE_BOOLEAN,
	.min = 0,
	.max = 1,
	.step = 1,
	.def = 0,
};

static int set_stream(struct v4l2_subdev *sd, int enable)
{
	return 0;
}

static const struct v4l2_subdev_video_ops csi2_be_sd_video_ops = {
	.s_stream = set_stream,
};

static int __subdev_link_validate(struct v4l2_subdev *sd,
				  struct media_link *link,
				  struct v4l2_subdev_format *source_fmt,
				  struct v4l2_subdev_format *sink_fmt)
{
	struct media_pipeline *mp = media_entity_pipeline(&sd->entity);
	struct ipu_isys_pipeline *ip = container_of(mp,
						    struct ipu_isys_pipeline,
						    pipe);

	ip->csi2_be = to_ipu_isys_csi2_be(sd);
	return ipu_isys_subdev_link_validate(sd, link, source_fmt, sink_fmt);
}

static int get_supported_code_index(u32 code)
{
	int i;

	for (i = 0; csi2_be_supported_codes_pad[i]; i++) {
		if (csi2_be_supported_codes_pad[i] == code)
			return i;
	}
	return -EINVAL;
}

static int ipu_isys_csi2_be_set_sel(struct v4l2_subdev *sd,
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
				    struct v4l2_subdev_pad_config *cfg,
#else
				    struct v4l2_subdev_state *state,
#endif
				    struct v4l2_subdev_selection *sel)
{
	struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd);
	struct media_pad *pad = &asd->sd.entity.pads[sel->pad];

	if (sel->target == V4L2_SEL_TGT_CROP &&
	    pad->flags & MEDIA_PAD_FL_SOURCE &&
	    asd->valid_tgts[CSI2_BE_PAD_SOURCE].crop) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
		struct v4l2_mbus_framefmt *ffmt =
			__ipu_isys_get_ffmt(sd, cfg, sel->pad, sel->which);
		struct v4l2_rect *r = __ipu_isys_get_selection
		    (sd, cfg, sel->target, CSI2_BE_PAD_SINK, sel->which);

#else
		struct v4l2_mbus_framefmt *ffmt =
			__ipu_isys_get_ffmt(sd, state, sel->pad, sel->which);
		struct v4l2_rect *r = __ipu_isys_get_selection
		    (sd, state, sel->target, CSI2_BE_PAD_SINK, sel->which);

#endif
		if (get_supported_code_index(ffmt->code) < 0) {
			/* Non-bayer formats can't be single line cropped */
			sel->r.left &= ~1;
			sel->r.top &= ~1;

			/* Non-bayer formats can't pe padded at all */
			sel->r.width = clamp(sel->r.width,
					     IPU_ISYS_MIN_WIDTH, r->width);
		} else {
			sel->r.width = clamp(sel->r.width,
					     IPU_ISYS_MIN_WIDTH,
					     IPU_ISYS_MAX_WIDTH);
		}

		/*
		 * Vertical padding is not supported, height is
		 * restricted by sink pad resolution.
		 */
		sel->r.height = clamp(sel->r.height, IPU_ISYS_MIN_HEIGHT,
				      r->height);
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
		*__ipu_isys_get_selection(sd, cfg, sel->target, sel->pad,
					  sel->which) = sel->r;
		ipu_isys_subdev_fmt_propagate
		    (sd, cfg, NULL, &sel->r,
		     IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_CROP,
		     sel->pad, sel->which);
#else
		*__ipu_isys_get_selection(sd, state, sel->target, sel->pad,
					  sel->which) = sel->r;
		ipu_isys_subdev_fmt_propagate
		    (sd, state, NULL, &sel->r,
		     IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_CROP,
		     sel->pad, sel->which);
#endif
		return 0;
	}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
	return ipu_isys_subdev_set_sel(sd, cfg, sel);
#else
	return ipu_isys_subdev_set_sel(sd, state, sel);
#endif
}

static const struct v4l2_subdev_pad_ops csi2_be_sd_pad_ops = {
	.link_validate = __subdev_link_validate,
	.get_fmt = ipu_isys_subdev_get_ffmt,
	.set_fmt = ipu_isys_subdev_set_ffmt,
	.get_selection = ipu_isys_subdev_get_sel,
	.set_selection = ipu_isys_csi2_be_set_sel,
	.enum_mbus_code = ipu_isys_subdev_enum_mbus_code,
};

static struct v4l2_subdev_ops csi2_be_sd_ops = {
	.core = &csi2_be_sd_core_ops,
	.video = &csi2_be_sd_video_ops,
	.pad = &csi2_be_sd_pad_ops,
};

static struct media_entity_operations csi2_be_entity_ops = {
	.link_validate = v4l2_subdev_link_validate,
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0)
static void csi2_be_set_ffmt(struct v4l2_subdev *sd,
			     struct v4l2_subdev_fh *cfg,
			     struct v4l2_subdev_format *fmt)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
static void csi2_be_set_ffmt(struct v4l2_subdev *sd,
			     struct v4l2_subdev_pad_config *cfg,
			     struct v4l2_subdev_format *fmt)
#else
static void csi2_be_set_ffmt(struct v4l2_subdev *sd,
			     struct v4l2_subdev_state *state,
			     struct v4l2_subdev_format *fmt)
#endif
{
	struct ipu_isys_csi2 *csi2 = to_ipu_isys_csi2(sd);
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
	struct v4l2_mbus_framefmt *ffmt =
		__ipu_isys_get_ffmt(sd, cfg, fmt->pad, fmt->which);

#else
	struct v4l2_mbus_framefmt *ffmt =
		__ipu_isys_get_ffmt(sd, state, fmt->pad, fmt->which);

#endif
	switch (fmt->pad) {
	case CSI2_BE_PAD_SINK:
		if (fmt->format.field != V4L2_FIELD_ALTERNATE)
			fmt->format.field = V4L2_FIELD_NONE;
		*ffmt = fmt->format;

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
		ipu_isys_subdev_fmt_propagate
		    (sd, cfg, &fmt->format, NULL,
		     IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, fmt->pad, fmt->which);
#else
		ipu_isys_subdev_fmt_propagate
		    (sd, state, &fmt->format, NULL,
		     IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, fmt->pad, fmt->which);
#endif
		return;
	case CSI2_BE_PAD_SOURCE: {
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
		struct v4l2_mbus_framefmt *sink_ffmt =
			__ipu_isys_get_ffmt(sd, cfg, CSI2_BE_PAD_SINK,
					    fmt->which);
		struct v4l2_rect *r =
			__ipu_isys_get_selection(sd, cfg, V4L2_SEL_TGT_CROP,
						 CSI2_BE_PAD_SOURCE,
						 fmt->which);
#else
		struct v4l2_mbus_framefmt *sink_ffmt =
			__ipu_isys_get_ffmt(sd, state, CSI2_BE_PAD_SINK,
					    fmt->which);
		struct v4l2_rect *r =
			__ipu_isys_get_selection(sd, state, V4L2_SEL_TGT_CROP,
						 CSI2_BE_PAD_SOURCE,
						 fmt->which);
#endif
		struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd);
		u32 code = sink_ffmt->code;
		int idx = get_supported_code_index(code);

		if (asd->valid_tgts[CSI2_BE_PAD_SOURCE].crop && idx >= 0) {
			int crop_info = 0;

			if (r->top & 1)
				crop_info |= CSI2_BE_CROP_VER;
			if (r->left & 1)
				crop_info |= CSI2_BE_CROP_HOR;
			code = csi2_be_supported_codes_pad
				[((idx & CSI2_BE_CROP_MASK) ^ crop_info)
				+ (idx & ~CSI2_BE_CROP_MASK)];
		}
		ffmt->width = r->width;
		ffmt->height = r->height;
		ffmt->code = code;
		ffmt->field = sink_ffmt->field;
		return;
	}
	default:
		dev_err(&csi2->isys->adev->dev, "Unknown pad type\n");
		WARN_ON(1);
	}
}

void ipu_isys_csi2_be_cleanup(struct ipu_isys_csi2_be *csi2_be)
{
	v4l2_ctrl_handler_free(&csi2_be->av.ctrl_handler);
	v4l2_device_unregister_subdev(&csi2_be->asd.sd);
	ipu_isys_subdev_cleanup(&csi2_be->asd);
	ipu_isys_video_cleanup(&csi2_be->av);
}

int ipu_isys_csi2_be_init(struct ipu_isys_csi2_be *csi2_be,
			  struct ipu_isys *isys)
{
	struct v4l2_subdev_format fmt = {
		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
		.pad = CSI2_BE_PAD_SINK,
		.format = {
			   .width = 4096,
			   .height = 3072,
			  },
	};
	struct v4l2_subdev_selection sel = {
		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
		.pad = CSI2_BE_PAD_SOURCE,
		.target = V4L2_SEL_TGT_CROP,
		.r = {
		      .width = fmt.format.width,
		      .height = fmt.format.height,
		     },
	};
	int rval;

	csi2_be->asd.sd.entity.ops = &csi2_be_entity_ops;
	csi2_be->asd.isys = isys;

	rval = ipu_isys_subdev_init(&csi2_be->asd, &csi2_be_sd_ops, 0,
				    NR_OF_CSI2_BE_PADS,
				    NR_OF_CSI2_BE_SOURCE_PADS,
				    NR_OF_CSI2_BE_SINK_PADS, 0,
				    CSI2_BE_PAD_SOURCE,
				    CSI2_BE_PAD_SINK);
	if (rval)
		goto fail;

	csi2_be->asd.pad[CSI2_BE_PAD_SINK].flags |= MEDIA_PAD_FL_MUST_CONNECT;
	csi2_be->asd.valid_tgts[CSI2_BE_PAD_SOURCE].crop = true;
	csi2_be->asd.set_ffmt = csi2_be_set_ffmt;

	BUILD_BUG_ON(ARRAY_SIZE(csi2_be_supported_codes) != NR_OF_CSI2_BE_PADS);
	csi2_be->asd.supported_codes = csi2_be_supported_codes;
	csi2_be->asd.be_mode = IPU_BE_RAW;
	csi2_be->asd.isl_mode = IPU_ISL_CSI2_BE;

	ipu_isys_subdev_set_ffmt(&csi2_be->asd.sd, NULL, &fmt);
	ipu_isys_csi2_be_set_sel(&csi2_be->asd.sd, NULL, &sel);

	csi2_be->asd.sd.internal_ops = &csi2_be_sd_internal_ops;
	snprintf(csi2_be->asd.sd.name, sizeof(csi2_be->asd.sd.name),
		 IPU_ISYS_ENTITY_PREFIX " CSI2 BE");
	snprintf(csi2_be->av.vdev.name, sizeof(csi2_be->av.vdev.name),
		 IPU_ISYS_ENTITY_PREFIX " CSI2 BE capture");
	csi2_be->av.aq.css_pin_type = IPU_FW_ISYS_PIN_TYPE_RAW_NS;
	v4l2_set_subdevdata(&csi2_be->asd.sd, &csi2_be->asd);
	rval = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2_be->asd.sd);
	if (rval) {
		dev_info(&isys->adev->dev, "can't register v4l2 subdev\n");
		goto fail;
	}

	csi2_be->av.isys = isys;
	csi2_be->av.pfmts = ipu_isys_pfmts;
	csi2_be->av.try_fmt_vid_mplane =
	    ipu_isys_video_try_fmt_vid_mplane_default;
	csi2_be->av.prepare_fw_stream =
	    ipu_isys_prepare_fw_cfg_default;
	csi2_be->av.aq.buf_prepare = ipu_isys_buf_prepare;
	csi2_be->av.aq.fill_frame_buff_set_pin =
	    ipu_isys_buffer_to_fw_frame_buff_pin;
	csi2_be->av.aq.link_fmt_validate = ipu_isys_link_fmt_validate;
	csi2_be->av.aq.vbq.buf_struct_size =
	    sizeof(struct ipu_isys_video_buffer);

	/* create v4l2 ctrl for csi-be video node */
	rval = v4l2_ctrl_handler_init(&csi2_be->av.ctrl_handler, 0);
	if (rval) {
		dev_err(&isys->adev->dev,
			"failed to init v4l2 ctrl handler for csi2_be\n");
		goto fail;
	}

	csi2_be->av.compression_ctrl =
		v4l2_ctrl_new_custom(&csi2_be->av.ctrl_handler,
				     &compression_ctrl_cfg, NULL);
	if (!csi2_be->av.compression_ctrl) {
		dev_err(&isys->adev->dev,
			"failed to create CSI-BE cmprs ctrl\n");
		goto fail;
	}
	csi2_be->av.compression = 0;
	csi2_be->av.vdev.ctrl_handler = &csi2_be->av.ctrl_handler;

	rval = ipu_isys_video_init(&csi2_be->av, &csi2_be->asd.sd.entity,
				   CSI2_BE_PAD_SOURCE, MEDIA_PAD_FL_SINK, 0);
	if (rval) {
		dev_info(&isys->adev->dev, "can't init video node\n");
		goto fail;
	}

	return 0;

fail:
	ipu_isys_csi2_be_cleanup(csi2_be);

	return rval;
}
