// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2022 MediaTek Inc.
 */

#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <media/dvbdev.h>

#include "mdrv_adapter.h"

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) "mtk-usbcam-stub: " fmt

#define USBCAM_ERR(fmt, ...)		\
	pr_err("[%s][%d]" fmt,	\
	__func__, __LINE__, ##__VA_ARGS__)

#define USBCAM_INFO(fmt, ...)		\
	pr_info("[%s][%d]" fmt,	\
	__func__, __LINE__, ##__VA_ARGS__)

#define BLK_SIZE		(4184 * 188) //768K
#define BUF_SIZE		(10)

struct dvb_usbcam_data {
	u8 *data;
	u32 size;
};

struct dvb_usbcam_private {
	struct dvb_device *dvbdev;
	bool ack;
	struct dvb_usbcam_data buf[BUF_SIZE];
	u32 wp, rp;
};

static const struct file_operations dvb_usbcam_fops = {
	.owner = THIS_MODULE,
};

static const struct dvb_device dvbdev_usbcam = {
	.priv = NULL,
	.users = 1,
	.readers = 1,
	.writers = 1,
#if defined(CONFIG_MEDIA_CONTROLLER_DVB) || defined(CONFIG_MTK_MEDIA_CONTROLLER_DVB)
	.name = "dvb-ca-usbcam",
#endif
	.fops = &dvb_usbcam_fops,
};

static struct dvb_usbcam_private *priv = NULL;
static struct dvbdev_notifier *notifier = NULL;

static int mdrv_usbcam_notifier_link_notify(struct dvb_device *dev,
	struct dvbdev_link *link)
{
	return 0;
}

static int mdrv_usbcam_notifier_query_notify(struct dvb_device *dev,
	struct dvbdev_properties *prop)
{
	u32 i, num = prop->num;

	for (i = 0; i < num; i++) {
		switch (prop->props[i].cmd) {
		case ADAP_CMD_CI2_BUFFER_INFO:
			if (priv->rp == BUF_SIZE)
				priv->rp = 0;
			prop->props[i].buffer.buf = (priv->buf[priv->rp].data);
			prop->props[i].buffer.len = priv->buf[priv->rp].size;
			(priv->rp)++;

			break;
		case ADAP_CMD_CI2_BUFFER_ACK:
			if (priv->ack) {
				prop->props[i].value = 1;
				priv->ack = false;
			}
			break;
		default:
			return -1;
		}
	}
	return 0;
}

static int mdrv_usbcam_notifier_streaming_event(struct dvb_device *dvbdev,
	enum cfm_notify_event_type_t ev_type,
	struct cfm_properties *props, void *notify_data)
{
	return 0;
}

static int mdrv_usbcam_notifier_data_ready(struct dvb_device *dev,
	void *addr, u32 size, void *notify_data)
{
	if (priv->wp == BUF_SIZE)
		priv->wp = 0;
	if (priv->buf[priv->wp].data == NULL)
		return 0;
	memset((priv->buf[priv->wp].data), 0, BLK_SIZE);
	if (size >= BLK_SIZE)
		size = BLK_SIZE;
	memcpy((priv->buf[priv->wp].data), addr, size);
	priv->buf[priv->wp].size = size;

	(priv->wp)++;
	priv->ack = true;

// @TODO
/*
 * usleep stand for usbcam work function
 */
	usleep_range(100, 200);

	return 0;
}

int mdrv_usbcam_stub_init(void)
{
	struct dvb_adapter *adapter = mdrv_get_dvb_adapter();
	int ret = 0, i;

	if (!adapter)
		return -ENODEV;

	priv = kzalloc(sizeof(struct dvb_usbcam_private), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	ret = dvb_register_device(adapter, &priv->dvbdev, &dvbdev_usbcam,
		priv, DVB_DEVICE_CA, 0);
	if (ret != 0) {
		USBCAM_ERR("register dvb device fail\n");
		return -ENODEV;
	}

	for (i = 0; i < BUF_SIZE; i++) {
		priv->buf[i].data = vmalloc(sizeof(u8) * BLK_SIZE);
		if (priv->buf[i].data)
			memset(priv->buf[i].data, 0x0, (sizeof(u8) * BLK_SIZE));
		else {
			USBCAM_ERR("vmalloc fail\n");
			return -ENOMEM;
		}
	}

	notifier = kzalloc(sizeof(struct dvbdev_notifier), GFP_KERNEL);
	if (notifier) {
		notifier->link_notify = &mdrv_usbcam_notifier_link_notify;
		notifier->query_notify = &mdrv_usbcam_notifier_query_notify;
		notifier->streaming_event_notify = &mdrv_usbcam_notifier_streaming_event;
		notifier->data_ready_notify = &mdrv_usbcam_notifier_data_ready;
		ret = mdrv_adapter_register_device_notifier(
			priv->dvbdev, notifier);
		if (ret < 0)
			USBCAM_ERR("register notifier fail\n");
	}
	return 0;
}

int mdrv_usbcam_stub_exit(void)
{
	int ret = 0, i;

	ret = mdrv_adapter_unregister_device_notifier(priv->dvbdev);
	if (ret < 0)
		USBCAM_ERR("unregister notifier fail\n");
	kfree(notifier);

	for (i = 0; i < BUF_SIZE; i++)
		vfree(priv->buf[i].data);

	dvb_unregister_device(priv->dvbdev);

	kfree(priv);
	return 0;
}
