/*
 * Samsung Exynos SoC series VPU driver
 *
 * Copyright (c) 2015 Samsung Electronics Co., Ltd
 *
 * 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.
 */


#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <asm/uaccess.h>

#include "vision-config.h"
#include "vision-dev.h"

static struct vision_device *vision_device[VISION_NUM_DEVICES];
static DEFINE_MUTEX(visiondev_lock);

/*****************************************************************************
 *****                         wrapper function                          *****
 *****************************************************************************/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0)
static int vision_flush(struct file *file, fl_owner_t id)
{
	struct vision_device *vdev = vision_devdata(file);

	if (!vdev->fops->flush)
		return -ENOENT;

	return vdev->fops->flush(file);
}

static int vision_log_probe_w(struct vision_device *vdev)
{
#if IS_ENABLED(CONFIG_EXYNOS_MEMORY_LOGGER)
	return vision_log_probe(vdev);
#else
	return 0;
#endif
}
#else
static int vision_flush(struct file *file, fl_owner_t id)
{
	return 0;
}

static int vision_log_probe_w(__attribute__((unused))struct vision_device *vdev)
{
	return 0;
}
#endif

struct vision_device *vision_devdata(struct file *file)
{
	return vision_device[iminor(file_inode(file))];
}

static int vision_open(struct inode *inode, struct file *file)
{
	int ret = 0;
	struct vision_device *vdev;

	mutex_lock(&visiondev_lock);

	vdev = vision_devdata(file);
	if (!vdev) {
		mutex_unlock(&visiondev_lock);
		vision_err("vision device is NULL\n");
		ret = -ENODEV;
		goto p_err;
	}

	if (!(vdev->flags & (1 << VISION_DEVICE_TYPE_VERTEX))) {
		mutex_unlock(&visiondev_lock);
		vision_err("[V] vision device is not registered\n");
		ret = -ENODEV;
		goto p_err;
	}

	get_device(&vdev->dev);
	mutex_unlock(&visiondev_lock);

	ret = (vdev->fops->open ? vdev->fops->open(file) : -EINVAL);
	if (ret) {
		put_device(&vdev->dev);
		goto p_err;
	}

p_err:
	return ret;
}

static int vision_release(struct inode *inode, struct file *file)
{
	int ret = 0;
	struct vision_device *vdev = vision_devdata(file);

	ret = (vdev->fops->release ? vdev->fops->release(file) : -EINVAL);
	put_device(&vdev->dev);

	return ret;
}

static long vision_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	struct vision_device *vdev = vision_devdata(file);
	struct mutex *lock = vdev->lock;

	if (!vdev->fops->ioctl) {
		vision_err("ioctl is not registered\n");
		ret = -ENOTTY;
		goto p_err;
	}

	if (lock && mutex_lock_interruptible(lock))
		return -ERESTARTSYS;

	ret = vdev->fops->ioctl(file, cmd, arg);

	if (lock)
		mutex_unlock(lock);

p_err:
	return ret;
}

static long vision_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	struct vision_device *vdev = vision_devdata(file);
	struct mutex *lock = vdev->lock;

	if (!vdev->fops->compat_ioctl) {
		vision_err("compat_ioctl is not registered\n");
		ret = -ENOTTY;
		goto p_err;
	}

	if (lock && mutex_lock_interruptible(lock))
		return -ERESTARTSYS;

	ret = vdev->fops->compat_ioctl(file, cmd, arg);

	if (lock)
		mutex_unlock(lock);

p_err:
	return ret;
}

static unsigned int vision_poll(struct file *file, struct poll_table_struct *poll)
{
	struct vision_device *vdev = vision_devdata(file);

	if (!vdev->fops->poll)
		return DEFAULT_POLLMASK;

	return vdev->fops->poll(file, poll);
}

static const struct file_operations vision_fops = {
	.owner = THIS_MODULE,
	.open = vision_open,
	.release = vision_release,
	.unlocked_ioctl = vision_ioctl,
	.compat_ioctl = vision_compat_ioctl32,
	.poll = vision_poll,
	.llseek = no_llseek,
	.flush = vision_flush,
};

static ssize_t index_show(struct device *dev,
			 struct device_attribute *attr, char *buf)
{
	struct vision_device *vdev = container_of(dev, struct vision_device, dev);

	return sprintf(buf, "%i\n", vdev->index);
}
static DEVICE_ATTR_RO(index);

static ssize_t debug_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct vision_device *vdev = container_of(dev, struct vision_device, dev);

	return sprintf(buf, "%i\n", vdev->debug);
}

static ssize_t debug_store(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t len)
{
	struct vision_device *vdev = container_of(dev, struct vision_device, dev);
	int res = 0;
	u16 value;

	res = kstrtou16(buf, 0, &value);
	if (res) {
		vision_err("kstrtou16 is fail\n");
		return res;
	}

	vdev->debug = value;
	return len;
}
static DEVICE_ATTR_RW(debug);

static ssize_t name_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct vision_device *vdev = container_of(dev, struct vision_device, dev);

	return sprintf(buf, "%.*s\n", (int)sizeof(vdev->name), vdev->name);
}
static DEVICE_ATTR_RO(name);

static ssize_t tpf_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct vision_device *vdev = container_of(dev, struct vision_device, dev);

	return sprintf(buf, "%lld\n", vdev->tpf);
}
static DEVICE_ATTR_RO(tpf);

static struct attribute *vision_device_attrs[] = {
	&dev_attr_name.attr,
	&dev_attr_debug.attr,
	&dev_attr_index.attr,
	&dev_attr_tpf.attr,
	NULL,
};
ATTRIBUTE_GROUPS(vision_device);

struct class vision_class = {
	.name = VISION_NAME,
	.dev_groups = vision_device_groups,
};

static void vision_device_release(struct device *dev)
{
	struct vision_device *vdev = container_of(dev, struct vision_device, dev);

	mutex_lock(&visiondev_lock);
	if (WARN_ON(vision_device[vdev->minor] != vdev)) {
		/* should not happen */
		mutex_unlock(&visiondev_lock);
		return;
	}

	/* Free up this device for reuse */
	vision_device[vdev->minor] = NULL;

	/* Delete the cdev on this minor as well */
	cdev_del(vdev->cdev);
	/* Just in case some driver tries to access this from
	 * the release() callback.
	 */
	vdev->cdev = NULL;

	mutex_unlock(&visiondev_lock);

	/* Release video_device and perform other
	 * cleanups as needed.
	 */
	vdev->release(vdev);
}

int vision_register_device(struct vision_device *vdev, int minor, struct module *owner)
{
	int ret = 0;
	const char *name_base;

	BUG_ON(!vdev->parent);
	WARN_ON(vision_device[minor] != NULL);

	vdev->cdev = NULL;

	switch (vdev->type) {
	case VISION_DEVICE_TYPE_VERTEX:
		name_base = "vertex";
		break;
	default:
		vprobe_err("%s called with unknown type: %d\n", __func__, vdev->type);
		return -EINVAL;
	}

	/* Part 3: Initialize the character device */
	vdev->cdev = cdev_alloc();
	if (vdev->cdev == NULL) {
		ret = -ENOMEM;
		goto cleanup;
	}
	vdev->cdev->ops = &vision_fops;
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VISION_MAJOR, minor), 1);
	if (ret < 0) {
		vprobe_err("%s: cdev_add failed\n", __func__);
		kfree(vdev->cdev);
		vdev->cdev = NULL;
		goto cleanup;
	}

	/* Part 4: register the device with sysfs */
	vdev->dev.class = &vision_class;
	vdev->dev.parent = vdev->parent;
	vdev->dev.devt = MKDEV(VISION_MAJOR, minor);
	dev_set_name(&vdev->dev, "%s%d", name_base, minor);
	ret = device_register(&vdev->dev);
	if (ret < 0) {
		vprobe_err("%s: device_register failed\n", __func__);
		goto cleanup;
	}
	/* Register the release callback that will be called when the last
	 * reference to the device goes away.
	 */
	vdev->dev.release = vision_device_release;

	ret = vision_log_probe_w(vdev);
	if (ret) {
		vprobe_err("%s: vsion_log_probe failed\n", __func__);
		goto cleanup;
	}

	/* Part 6: Activate this minor. The char device can now be used. */
	set_bit(VISION_FL_REGISTERED, &vdev->flags);

	mutex_lock(&visiondev_lock);
	vdev->minor = minor;
	vision_device[minor] = vdev;
	mutex_unlock(&visiondev_lock);

	return 0;

cleanup:
	mutex_lock(&visiondev_lock);
	if (vdev->cdev)
		cdev_del(vdev->cdev);
	/* Mark this video device as never having been registered. */
	vdev->minor = -1;
	mutex_unlock(&visiondev_lock);
	return ret;
}
EXPORT_SYMBOL(vision_register_device);

//#ifdef CONFIG_NPU_BUILD_BUILTIN
//static int __init visiondev_init(void)
//{
//	int ret;
//	dev_t dev = MKDEV(VISION_MAJOR, 0);
//
//	vision_info("Linux vision interface: v1.00\n");
//	ret = register_chrdev_region(dev, VISION_NUM_DEVICES, VISION_NAME);
//	if (ret < 0) {
//		vision_err("videodev: unable to get major %d\n", VISION_MAJOR);
//		return ret;
//	}
//
//	ret = class_register(&vision_class);
//	if (ret < 0) {
//		unregister_chrdev_region(dev, VISION_NUM_DEVICES);
//		vision_err("video_dev: class_register failed\n");
//		return -EIO;
//	}
//
//	return 0;
//}
//
//static void __exit visiondev_exit(void)
//{
//	dev_t dev = MKDEV(VISION_MAJOR, 0);
//
//	class_unregister(&vision_class);
//	unregister_chrdev_region(dev, VISION_NUM_DEVICES);
//}
//
//subsys_initcall(visiondev_init);
//module_exit(visiondev_exit)
//
//MODULE_AUTHOR("Gilyeon Lim <kilyeon.im@samsung.com>");
//MODULE_DESCRIPTION("Device registrar for Vision4Linux drivers v1");
//MODULE_LICENSE("GPL");
//MODULE_ALIAS_CHARDEV_MAJOR(VISION_MAJOR);
//#endif