// SPDX-License-Identifier: GPL-2.0
/*
 * Support for Medifield PNW Camera Imaging ISP subsystem.
 *
 * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
 *
 * Copyright (c) 2010 Silicon Hive www.siliconhive.com.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *
 */

#include <media/v4l2-event.h>
#include <media/v4l2-mediabus.h>

#include <media/videobuf-vmalloc.h>
#include <linux/delay.h>

#include "ia_css.h"

#include "atomisp_cmd.h"
#include "atomisp_common.h"
#include "atomisp_file.h"
#include "atomisp_internal.h"
#include "atomisp_ioctl.h"

static void file_work(struct work_struct *work)
{
	struct atomisp_file_device *file_dev =
	    container_of(work, struct atomisp_file_device, work);
	struct atomisp_device *isp = file_dev->isp;
	/* only support file injection on subdev0 */
	struct atomisp_sub_device *asd = &isp->asd[0];
	struct atomisp_video_pipe *out_pipe = &asd->video_in;
	unsigned short *buf = videobuf_to_vmalloc(out_pipe->outq.bufs[0]);
	struct v4l2_mbus_framefmt isp_sink_fmt;

	if (asd->streaming != ATOMISP_DEVICE_STREAMING_ENABLED)
		return;

	dev_dbg(isp->dev, ">%s: ready to start streaming\n", __func__);
	isp_sink_fmt = *atomisp_subdev_get_ffmt(&asd->subdev, NULL,
						V4L2_SUBDEV_FORMAT_ACTIVE,
						ATOMISP_SUBDEV_PAD_SINK);

	while (!ia_css_isp_has_started())
		usleep_range(1000, 1500);

	ia_css_stream_send_input_frame(asd->stream_env[ATOMISP_INPUT_STREAM_GENERAL].stream,
				       buf, isp_sink_fmt.width,
				       isp_sink_fmt.height);
	dev_dbg(isp->dev, "<%s: streaming done\n", __func__);
}

static int file_input_s_stream(struct v4l2_subdev *sd, int enable)
{
	struct atomisp_file_device *file_dev = v4l2_get_subdevdata(sd);
	struct atomisp_device *isp = file_dev->isp;
	/* only support file injection on subdev0 */
	struct atomisp_sub_device *asd = &isp->asd[0];

	dev_dbg(isp->dev, "%s: enable %d\n", __func__, enable);
	if (enable) {
		if (asd->streaming != ATOMISP_DEVICE_STREAMING_ENABLED)
			return 0;

		queue_work(file_dev->work_queue, &file_dev->work);
		return 0;
	}
	cancel_work_sync(&file_dev->work);
	return 0;
}

static int file_input_get_fmt(struct v4l2_subdev *sd,
			      struct v4l2_subdev_pad_config *cfg,
			      struct v4l2_subdev_format *format)
{
	struct v4l2_mbus_framefmt *fmt = &format->format;
	struct atomisp_file_device *file_dev = v4l2_get_subdevdata(sd);
	struct atomisp_device *isp = file_dev->isp;
	/* only support file injection on subdev0 */
	struct atomisp_sub_device *asd = &isp->asd[0];
	struct v4l2_mbus_framefmt *isp_sink_fmt;

	if (format->pad)
		return -EINVAL;
	isp_sink_fmt = atomisp_subdev_get_ffmt(&asd->subdev, NULL,
					       V4L2_SUBDEV_FORMAT_ACTIVE,
					       ATOMISP_SUBDEV_PAD_SINK);

	fmt->width = isp_sink_fmt->width;
	fmt->height = isp_sink_fmt->height;
	fmt->code = isp_sink_fmt->code;

	return 0;
}

static int file_input_set_fmt(struct v4l2_subdev *sd,
			      struct v4l2_subdev_pad_config *cfg,
			      struct v4l2_subdev_format *format)
{
	struct v4l2_mbus_framefmt *fmt = &format->format;

	if (format->pad)
		return -EINVAL;
	file_input_get_fmt(sd, cfg, format);
	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
		cfg->try_fmt = *fmt;
	return 0;
}

static int file_input_log_status(struct v4l2_subdev *sd)
{
	/*to fake*/
	return 0;
}

static int file_input_s_power(struct v4l2_subdev *sd, int on)
{
	/* to fake */
	return 0;
}

static int file_input_enum_mbus_code(struct v4l2_subdev *sd,
				     struct v4l2_subdev_pad_config *cfg,
				     struct v4l2_subdev_mbus_code_enum *code)
{
	/*to fake*/
	return 0;
}

static int file_input_enum_frame_size(struct v4l2_subdev *sd,
				      struct v4l2_subdev_pad_config *cfg,
				      struct v4l2_subdev_frame_size_enum *fse)
{
	/*to fake*/
	return 0;
}

static int file_input_enum_frame_ival(struct v4l2_subdev *sd,
				      struct v4l2_subdev_pad_config *cfg,
				      struct v4l2_subdev_frame_interval_enum
				      *fie)
{
	/*to fake*/
	return 0;
}

static const struct v4l2_subdev_video_ops file_input_video_ops = {
	.s_stream = file_input_s_stream,
};

static const struct v4l2_subdev_core_ops file_input_core_ops = {
	.log_status = file_input_log_status,
	.s_power = file_input_s_power,
};

static const struct v4l2_subdev_pad_ops file_input_pad_ops = {
	.enum_mbus_code = file_input_enum_mbus_code,
	.enum_frame_size = file_input_enum_frame_size,
	.enum_frame_interval = file_input_enum_frame_ival,
	.get_fmt = file_input_get_fmt,
	.set_fmt = file_input_set_fmt,
};

static const struct v4l2_subdev_ops file_input_ops = {
	.core = &file_input_core_ops,
	.video = &file_input_video_ops,
	.pad = &file_input_pad_ops,
};

void
atomisp_file_input_unregister_entities(struct atomisp_file_device *file_dev)
{
	media_entity_cleanup(&file_dev->sd.entity);
	v4l2_device_unregister_subdev(&file_dev->sd);
}

int atomisp_file_input_register_entities(struct atomisp_file_device *file_dev,
	struct v4l2_device *vdev)
{
	/* Register the subdev and video nodes. */
	return  v4l2_device_register_subdev(vdev, &file_dev->sd);
}

void atomisp_file_input_cleanup(struct atomisp_device *isp)
{
	struct atomisp_file_device *file_dev = &isp->file_dev;

	if (file_dev->work_queue) {
		destroy_workqueue(file_dev->work_queue);
		file_dev->work_queue = NULL;
	}
}

int atomisp_file_input_init(struct atomisp_device *isp)
{
	struct atomisp_file_device *file_dev = &isp->file_dev;
	struct v4l2_subdev *sd = &file_dev->sd;
	struct media_pad *pads = file_dev->pads;
	struct media_entity *me = &sd->entity;

	file_dev->isp = isp;
	file_dev->work_queue = alloc_workqueue(isp->v4l2_dev.name, 0, 1);
	if (!file_dev->work_queue) {
		dev_err(isp->dev, "Failed to initialize file inject workq\n");
		return -ENOMEM;
	}

	INIT_WORK(&file_dev->work, file_work);

	v4l2_subdev_init(sd, &file_input_ops);
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	strscpy(sd->name, "file_input_subdev", sizeof(sd->name));
	v4l2_set_subdevdata(sd, file_dev);

	pads[0].flags = MEDIA_PAD_FL_SINK;
	me->function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;

	return media_entity_pads_init(me, 1, pads);
}