/*
 * Copyright (C) 2021 Alibaba Group Holding Limited
 * Author: liuyitong <yitong.lyt@alibaba-inc.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.
 */

#include <asm/io.h>

#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/mm.h>

#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/poll.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/debugfs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include "video.h"
#include "video_ioctl.h"

#define VIDEO_DRIVER_NAME "video"
#define VIDEO_DRIVER_MAXCNT 100


static struct class *video_driver_class;
static unsigned int video_driver_major = 0;
static unsigned int video_driver_minor = 0;
static unsigned int device_register_index = 0;

#define check_retval(x)\
	do {\
		if ((x))\
			return -EIO;\
	} while (0)

static unsigned int video_poll(struct file * filp, poll_table *wait)
{
	return 0;
}

void video_work(struct work_struct *work)
{

}

irqreturn_t video_irq(int irq, void *dev_id)
{
	video_info("enter %s\n", __func__);
	return IRQ_HANDLED;
}

static int video_open(struct inode * inode, struct file * file)
{
	struct video_drvdata *drvdata;
	drvdata = container_of(inode->i_cdev, struct video_drvdata, cdev);
	file->private_data = drvdata;

	return 0;
};

static int video_get_pipelines(struct video_drvdata *drvdata, void *__user args)
{
    pipline_t *usr_pip = (pipline_t *)args;
    pipline_t *cam_pip = drvdata->piplines;

    check_retval(copy_to_user(usr_pip, cam_pip, sizeof(pipline_t) * drvdata->pipline_num));
	video_info("%s, %d, pipeline nmu: %d\n", __func__, __LINE__, drvdata->pipline_num);
    return 0;
}

static int video_get_pipelines_num (struct video_drvdata *drvdata, void *__user args)
{
    check_retval(copy_to_user(args, &drvdata->pipline_num, sizeof(int)));
    return 0;
}

static int video_set_path_type(struct video_drvdata *drvdata, void *__user args)
{
	pathtype_set_t pathtype_set;
	check_retval(copy_from_user(&pathtype_set, args, sizeof(pathtype_set_t)));
	video_info("VIDEO_SET_PATH_TYPE pipeline=%d sensor=%s path_type%d \n", pathtype_set.pipeline_id , pathtype_set.sensor_name, pathtype_set.path_type);
	return 0;
}

static int video_get_mem_pool_region_id(struct video_drvdata *drvdata, void *__user args)
{
	check_retval(copy_to_user(args, &drvdata->vi_mem_pool_region_id, sizeof(int)));
	return 0;
}
static long video_set_state(struct video_drvdata *drvdata,int *arg)
{
    int ops;
    check_retval(copy_from_user(&ops,arg,sizeof(int)));
    if(ops ==DEVICE_OPERATION_ACTIVE)
    {
        if(drvdata->actived == true)
        {
            dev_warn(drvdata->dev,"%s,can't ative,device already active by other process(%d)\n",__func__, current->tgid);
            return (-EBUSY);
        }else
        {
            drvdata->actived =true;
            drvdata->actived_owner = current->tgid;
            dev_dbg(drvdata->dev,"%s,actived by process %d\n",__func__,current->tgid);
        }
    }
    else if(ops ==DEVICE_OPERATION_DEACTIVE)
    {
        if(drvdata->actived == false)
        {
            dev_warn(drvdata->dev,"%s,can't deative,device is not atived\n",__func__);
            return (-EPERM);
        }else
        {
            if(drvdata->actived_owner != current->tgid)
            {
                dev_warn(drvdata->dev,"%s,can't deative,device is active by other process(%d)\n",__func__, current->tgid);
                return (-EPERM);
            }
            drvdata->actived =false;
            drvdata->actived_owner = -1;
            dev_dbg(drvdata->dev,"%s de-active",__func__);
        }
    }else
    {
        dev_warn(drvdata->dev,"%s,un-defined ops\n",__func__);
        return (-EPERM);
    }

    return 0;
}

static long video_get_state(struct video_drvdata *drvdata,int *arg)
{
        int ops;
        ops = drvdata->actived==true?DEVICE_OPERATION_ACTIVE:DEVICE_OPERATION_DEACTIVE;
        check_retval(copy_to_user(arg, &ops, sizeof(int)));
        return 0;
}
static long video_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	long ret = 0;
	struct video_drvdata *drvdata;

	drvdata = file->private_data;
	if (drvdata == NULL) {
		video_err("%s:file private is null point error\n", __func__);
		return  -ENOMEM;
	}

	mutex_lock(&drvdata->mutex);
	switch (cmd) {
	case VIDEO_GET_PIPLANES:
		ret = video_get_pipelines(drvdata, (void *)arg);
		break;
	case VIDEO_GET_PIPLANES_NUM:
		ret = video_get_pipelines_num(drvdata, (void *)arg);
		break;
	case VIDEO_SET_PATH_TYPE:
		ret = video_set_path_type(drvdata, (void *)arg);
		break;
	case VIDEO_GET_MEM_POOL_REGION_ID:
		ret = video_get_mem_pool_region_id(drvdata, (void *)arg);
		break;
    case VIDEO_SET_DEVICE_STATE:
        ret = video_set_state(drvdata, (void *)arg);
        break;
    case VIDEO_GET_DEVICE_STATE:
        ret = video_get_state(drvdata, (void *)arg);
        break;
	default:
		ret = -EPERM;
		video_err("%s: unsupported command %d\n", __func__, cmd);
		break;
	}
	mutex_unlock(&drvdata->mutex);
	return ret;
};

static int video_release(struct inode * inode, struct file * file)
{
    struct video_drvdata *drvdata;
	drvdata = container_of(inode->i_cdev, struct video_drvdata, cdev);
    mutex_lock(&drvdata->mutex);
    if(drvdata->actived == true &&
        drvdata->actived_owner == current->tgid)
    {
        drvdata->actived = false;
        drvdata->actived_owner = -1;
        dev_dbg(drvdata->dev,"%s de-active device\n",__func__);
    }
    mutex_unlock(&drvdata->mutex);

    return 0;
};

static int video_mmap(struct file *pFile, struct vm_area_struct *vma)
{
	video_info("enter %s\n", __func__);
	return 0;
};

struct file_operations video_fops = {
	.owner = THIS_MODULE,
	.open = video_open,
	.release = video_release,
	.unlocked_ioctl = video_ioctl,
	.mmap = video_mmap,
	.poll = video_poll,
};

static int video_press_pipline(pipline_t *pipline, struct fwnode_handle *pip_node)
{
    int ret = 0;
    const char *dev_name;
	const char *path_name;
    struct fwnode_handle *nodes;
    sub_dev_info_t *sub_dev = pipline->sub_dev;


    fwnode_for_each_available_child_node(pip_node, nodes) {
        ret = fwnode_property_read_string(nodes, "subdev_name", &dev_name);
        if (ret != 0) {
            video_err("dts press error!!!!!, %s, %d\n", __func__, __LINE__);
            return -1;
        }

        strcpy(sub_dev->name, dev_name);

        ret = fwnode_property_read_u32_array(nodes, "idx", &sub_dev->idx, 1);
        if (ret != 0) {
	        video_err("dts press  device :%s error!!!!!, %d, %s\n", __func__, __LINE__,dev_name);
            return -1;
        }

		ret = fwnode_property_read_string(nodes, "path_type", &path_name);
		if (ret != 0) {
	        video_err("%s, %d, dts parse %s->%s error!!!!!\n", __func__, __LINE__, dev_name, path_name);
            return -1;
        }

        video_info("%s, %d, dev: %s->%s !!!!!\n", __func__, __LINE__, dev_name, path_name);
		sub_dev->path = vedio_get_path_type(dev_name, path_name);
		if(sub_dev->path < 0) {
            video_err("%s, %d, %s, parse path(%s) error!!!!!\n", __func__, __LINE__, dev_name, path_name);
            return -1;
		}
        video_info("subdev:%s-%d,path:(%s,%d)\n",dev_name,sub_dev->idx, path_name,sub_dev->path);

        ret = video_subdev_press_dts(nodes, sub_dev);
        if (ret != 0) {
	        video_err("dts press error!!!!!, %s, %d\n", __func__, __LINE__);
            return -1;
        }

        sub_dev++;
        pipline->sub_dev_num++;
    }

    return 0;
}

static int video_press_dts(struct platform_device *pdev)
{
	int ret = 0;
	struct device *dev = &pdev->dev;
	struct fwnode_handle *child;
	struct video_drvdata *drvdata;
	pipline_t *pipline;
	int pool_region_id;

	drvdata  = platform_get_drvdata(pdev);
	pipline = drvdata->piplines;

	//if (device_property_read_u32(&(pdev->dev), "vi_mem_pool_region", &pool_region_id) != 0) {
	if (of_property_read_s32(dev->of_node, "vi_mem_pool_region", &pool_region_id) != 0) {
		video_warn("%s, dev(%s) can't get vi_mem_pool_region id from dts, set to default -1(cma)\n",
				   __func__, pdev->name);
		pool_region_id = -1;
	}
	drvdata->vi_mem_pool_region_id = pool_region_id;

	device_for_each_child_node(dev, child) {
		video_info("pipleline(%d):",drvdata->pipline_num);
		ret = video_press_pipline(pipline, child);
		if (ret != 0) {
			video_err("%s, %d, dts press pipe:%d error!\n", __func__, __LINE__, drvdata->pipline_num);
			return -1;
		}
		pipline++;
		drvdata->pipline_num++;
	}

	return 0;
}

static int video_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct video_drvdata *drvdata;
	struct device_node *np = pdev->dev.of_node;

	video_info("enter %s\n", __func__);
	pdev->id = of_alias_get_id(np, "viv_video");

	video_info("%s:pdev id is %d\n", __func__, pdev->id);

	if (pdev->id >= VIDEO_DRIVER_MAXCNT) {
		video_err("%s:pdev id is %d error\n", __func__, pdev->id);
		return  -EINVAL;
	}

	drvdata = devm_kzalloc(&pdev->dev,sizeof(struct video_drvdata), GFP_KERNEL);
	if (drvdata == NULL) {
		video_err("%s:alloc struct drvdata error\n", __func__);
		return  -ENOMEM;
	}

	drvdata->device_idx = pdev->id;
    drvdata->actived = false;
    drvdata->actived_owner = -1;
    drvdata->dev = &pdev->dev;
	mutex_init(&drvdata->mutex);
	platform_set_drvdata(pdev, drvdata);

	if (device_register_index == 0) {
		if (video_driver_major == 0) {
			ret = alloc_chrdev_region(&drvdata->devt, 0, VIDEO_DRIVER_MAXCNT, VIDEO_DRIVER_NAME);
			if (ret != 0) {
				video_err("%s:alloc_chrdev_region error\n", __func__);
				return ret;
			}
			video_driver_major = MAJOR(drvdata->devt);
			video_driver_minor = MINOR(drvdata->devt);
		} else {
			drvdata->devt = MKDEV(video_driver_major, video_driver_minor);
			ret = register_chrdev_region(drvdata->devt, VIDEO_DRIVER_MAXCNT, VIDEO_DRIVER_NAME);
			if (ret) {
				video_err("%s:register_chrdev_region error\n", __func__);
				return ret;
			}
		}

		video_driver_class = class_create(THIS_MODULE, VIDEO_DRIVER_NAME);
		if (IS_ERR(video_driver_class)) {
			video_err("%s[%d]:class_create error!\n", __func__, __LINE__);
			return -EINVAL;
		}
	}

	drvdata->devt = MKDEV(video_driver_major, video_driver_minor + pdev->id);
	cdev_init(&drvdata->cdev, &video_fops);
	ret = cdev_add(&drvdata->cdev, drvdata->devt, 1);
	if ( ret ) {
		video_err("%s[%d]:cdev_add error!\n", __func__, __LINE__);
		return ret;
	} else {
		video_info("%s[%d]:cdev_add OK, major=%d, minor=%d\n", __func__, __LINE__,
			video_driver_major, video_driver_minor + pdev->id);
	}

	drvdata->class = video_driver_class;
	device_create(drvdata->class, NULL, drvdata->devt,
		      drvdata, "%s%d", VIDEO_DRIVER_NAME, pdev->id);

    ret = video_press_dts(pdev);
	if (ret) {
		video_err("%s[%d]:dts error!\n", __func__, __LINE__);
		return ret;
    }

    video_create_capabilities_sysfs(pdev);
	device_register_index++;

	video_info("exit %s:[%s%d]\n", __func__, VIDEO_DRIVER_NAME, pdev->id);

	return 0;
}

static int video_remove(struct platform_device *pdev)
{
	struct video_drvdata *drvdata;

	video_info("enter %s\n", __func__);
	device_register_index--;
	drvdata = platform_get_drvdata(pdev);
	cdev_del(&drvdata->cdev);
	device_destroy(drvdata->class, drvdata->devt);
	unregister_chrdev_region(drvdata->devt, VIDEO_DRIVER_MAXCNT);
	mutex_destroy(&drvdata->mutex);
    video_remove_capabilities_sysfs(pdev);
	if (device_register_index == 0) {
		class_destroy(drvdata->class);
	}
	devm_kfree(&pdev->dev, drvdata);
	video_info("exit %s\n", __func__);
	return 0;
}

static const struct of_device_id video_of_match[] = {
	{ .compatible = "thead,video", },
	{ /* sentinel */ },
};

static struct platform_driver video_driver = {
	.probe		= video_probe,
	.remove		= video_remove,
	.driver = {
		.name  = VIDEO_DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(video_of_match),
	}
};

static int __init video_init_module(void)
{
	int ret = 0;

	video_info("enter %s\n", __func__);
	ret = platform_driver_register(&video_driver);
	if (ret) {
		video_err("register platform driver failed.\n");
		return ret;
	}

	return ret;
}

static void __exit video_exit_module(void)
{
	video_info("enter %s\n", __func__);
	platform_driver_unregister(&video_driver);
}

module_init(video_init_module);
module_exit(video_exit_module);

MODULE_AUTHOR("Liu Yitong");
MODULE_DESCRIPTION("THEAD-VIDEO");
MODULE_LICENSE("GPL");
