/* rts_g2d_regs.h
 *
 * Copyright(c) 2019 Realtek Semiconductor Corp.
 *
 * @author shawn_chen@realsil.com.cn
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
#include <linux/of_irq.h>
#include <linux/clk.h>
#include <linux/reset.h>

#include "rts_dbg.h"
#include "rts_g2d_driver.h"
#include "rts_g2d.h"

#define DRV_NAME "rts_g2d"
#define MISCDEV_MINOR	195
#define MISCDEV_NAME G2D_DEVNAME
#define DRV_VERSION "1.0"
#define DRV_RELDATE	"March 10, 2019"

static char version[] =
DRV_NAME ": Realtek 2D Graphic Driver v" DRV_VERSION " (" DRV_RELDATE ")\n";

int debug;

struct rts_g2d_dev *rts_g2d_device;

struct mem_slot {
	void *mem_virt;		/* Virtual address */
	dma_addr_t mem_phy;	/* Physical address */
	u32 mem_len;		/* Length of allocted mem */
};

#define MAX_SLOTS 10
static struct mem_slot mem_slots[MAX_SLOTS];

static int rts_g2d_mem_request(struct device *dev, __u32 size)
{
	int i;

	for (i = 0; i < MAX_SLOTS; i++) {
		if (mem_slots[i].mem_virt == NULL)
			break;
	}

	if (i == MAX_SLOTS)
		return -1;

	mem_slots[i].mem_virt
	    = dma_alloc_coherent(dev, size, &mem_slots[i].mem_phy, GFP_KERNEL);
	if (!mem_slots[i].mem_virt) {
		dev_err(dev, "dma alloc failed\n");
		return -ENOMEM;
	}
	mem_slots[i].mem_len = size;
	memset(mem_slots[i].mem_virt, 0, size);

	dev_info(dev, "alloced mem slot %d: pa=0x%08lx va=0x%p size:0x%x\n",
		i, (unsigned long int)mem_slots[i].mem_phy,
		mem_slots[i].mem_virt, size);

	return i;
}

static int rts_g2d_mem_release(struct device *dev, __u32 sel)
{
	if (sel < 0)
		return -EINVAL;

	if (mem_slots[sel].mem_virt == NULL)
		return -EINVAL;

	if (mem_slots[sel].mem_virt)
		dma_free_coherent(dev, mem_slots[sel].mem_len,
				 mem_slots[sel].mem_virt, mem_slots[sel].mem_phy);

	dev_info(dev, "freed pa=0x%08lx va=0x%p size:0x%x\n",
		(unsigned long int)mem_slots[sel].mem_phy,
		mem_slots[sel].mem_virt, mem_slots[sel].mem_len);

	memset(&mem_slots[sel], 0, sizeof(struct mem_slot));

	return 0;
}

static int rts_g2d_mmap(struct file *file, struct vm_area_struct *vma)
{
	unsigned long mypfn = vma->vm_pgoff;
	unsigned long vmsize = vma->vm_end - vma->vm_start;

	vma->vm_pgoff = 0;

	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
	if (remap_pfn_range
	    (vma, vma->vm_start, mypfn, vmsize, vma->vm_page_prot))
		return -EAGAIN;

	return 0;
}

static int rts_g2d_open(struct inode *inode, struct file *file)
{
	file->private_data = rts_g2d_device;
	return 0;
}

static int rts_g2d_release(struct inode *inode, struct file *file)
{
	return 0;
}

static long rts_g2d_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	__s32	ret = 0;
	struct rts_g2d_dev *dev = (struct rts_g2d_dev *)(file->private_data);

	switch (cmd) {
	case G2D_CMD_BITBLT:
	{
		struct g2d_blt blit_para;

		if (copy_from_user(&blit_para, (struct g2d_blt __user *)arg, sizeof(struct g2d_blt))) {
			kfree(&blit_para);
			ret = -EFAULT;
		}
	    ret = rts_g2d_blit(&blit_para);
		break;
	}
	case G2D_CMD_STRETCH:
	{
		struct g2d_stretch stretch_para;

		if (copy_from_user(&stretch_para, (struct g2d_stretch __user *)arg,
			sizeof(struct g2d_stretch))) {
			kfree(&stretch_para);
			ret = -EFAULT;
		}
	    ret = rts_g2d_stretch(&stretch_para);
		break;
	}
	case G2D_CMD_ROTATE:
	{
		struct g2d_rotate rotate_para;

		if (copy_from_user(&rotate_para, (struct g2d_rotate __user *)arg,
			sizeof(struct g2d_rotate))) {
			kfree(&rotate_para);
			ret = -EFAULT;
		}
	    ret = rts_g2d_rotate(&rotate_para);
		break;
	}
	case G2D_CMD_MIRROR:
	{
		struct g2d_mirror mirror_para;

		if (copy_from_user(&mirror_para, (struct g2d_mirror __user *)arg,
			sizeof(struct g2d_mirror))) {
			kfree(&mirror_para);
			ret = -EFAULT;
		}
	    ret = rts_g2d_mirror(&mirror_para);
		break;
	}
	case G2D_CMD_FILLRECT:
	{
		struct g2d_fillrect fillrect_para;

		if (copy_from_user(&fillrect_para, (struct g2d_fillrect __user *)arg,
			sizeof(struct g2d_fillrect))) {
			kfree(&fillrect_para);
			ret = -EFAULT;
		}
	    ret = rts_g2d_fillrect(&fillrect_para);
		break;
	}
	case G2D_CMD_DRAWRECT:
	{
		struct g2d_fillrect fillrect_para;

		if (copy_from_user(&fillrect_para, (struct g2d_fillrect __user *)arg,
			sizeof(struct g2d_fillrect))) {
			kfree(&fillrect_para);
			ret = -EFAULT;
		}
	    ret = rts_g2d_drawrect(&fillrect_para);
		break;
	}
	case G2D_CMD_FORMAT_CONV:
	{
		struct g2d_formatconv formatconv_para;

		if (copy_from_user(&formatconv_para, (struct g2d_formatconv __user *)arg,
			sizeof(struct g2d_formatconv))) {
			kfree(&formatconv_para);
			ret = -EFAULT;
		}
	    ret = rts_g2d_formatconv(&formatconv_para);
		break;
	}
	case G2D_CMD_NOP:
	{
	    ret = rts_g2d_nop_test();
		break;
	}
	case G2D_CMD_PATCH16BPP:
	{
		ret = rts_g2d_patch16bpp();
		break;
	}
	/* just management memory for test */
	case G2D_CMD_MEM_REQUEST:
	{
		__u32 karg[1];

		if (copy_from_user((void *)karg, (const void __user *)arg,
			1 * sizeof(__u32))) {
			ret = -EFAULT;
			ERR("[SE]SE_IOC_MEM_REQUEST get value fail!\n");
		} else {
			if (karg[0] <= 0)
				return -EFAULT;

			ret = rts_g2d_mem_request(&dev->pdev->dev, karg[0]);
		}
		break;
	}
	case G2D_CMD_MEM_RELEASE:
	{
		__u32 sel;

		if (copy_from_user((void *)&sel, (const void __user *)arg,
			sizeof(__u32))) {
			ret = -EFAULT;
			ERR("[SE]SE_IOC_MEM_RELEASE get value fail!\n");
		} else {
			ret = rts_g2d_mem_release(&dev->pdev->dev, sel);
		}

		break;
	}
	case G2D_CMD_MEM_GETADR:
	{
		__u32 karg[1];

		if (copy_from_user((void *)karg, (const void __user *)arg,
			1 * sizeof(__u32))) {
			ret = -EFAULT;
			ERR("[SE]SE_IOC_MEM_GETADDR get value fail!\n");
		} else {
			if (mem_slots[karg[0]].mem_phy) {
				ret = mem_slots[karg[0]].mem_phy;
			} else {
				ERR("mem not used in SE_IOC_MEM_GETADR\n");
			    ret = -1;
			}
		}

		break;
	}
	default:
		break;
	}

	return ret;
}

static int rts_g2d_debug_get(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", debug ? "on" : "off");
}

static int rts_g2d_debug_set(struct device *dev,
				 struct device_attribute *attr,
				 const char *buf, size_t len)
{
	if (len < 1)
		return -EINVAL;

	if (strncasecmp(buf, "on", 2) == 0 ||
	    strncasecmp(buf, "1", 1) == 0) {
		debug = 1;
		dev_dbg(dev, "rts g2d: Debug On");
	} else if (strncasecmp(buf, "off", 3) == 0 ||
		   strncasecmp(buf, "0", 1) == 0) {
		debug = 0;
		dev_dbg(dev, "rts g2d: Debug Off");
	} else {
		return -EINVAL;
	}

	return len;
}

static DEVICE_ATTR(debug, 0664, rts_g2d_debug_get, rts_g2d_debug_set);

static struct attribute *g2d_attrs[] = {
	&dev_attr_debug.attr,
	NULL,
};

static struct attribute_group g2d_attribute_group = {
	.attrs = g2d_attrs,
	.name = "g2d"
};

static struct file_operations rts_g2d_fops = {
	.owner				= THIS_MODULE,
	.open				= rts_g2d_open,
	.release			= rts_g2d_release,
	.unlocked_ioctl		= rts_g2d_ioctl,
	.mmap				= rts_g2d_mmap,
};

static struct miscdevice fellow_misc = {
	.minor = MISCDEV_MINOR,
	.name = MISCDEV_NAME,
	.fops = &rts_g2d_fops,
};

static int rts_g2d_probe(struct platform_device *pdev)
{
	int	ret = 0;
	struct resource	*res;
	struct device *dev = &pdev->dev;

	INFO("%s", version);

	rts_g2d_device = devm_kzalloc(dev, sizeof(struct rts_g2d_dev), GFP_KERNEL);
	if (!rts_g2d_device)
		return -ENOMEM;

	rts_g2d_device->pdev = pdev;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(dev, "unable to get regs address\n");
		return -ENXIO;
	}

	rts_g2d_device->regmap_base = devm_ioremap_resource(dev, res);
	if (IS_ERR(rts_g2d_device->regmap_base)) {
		dev_err(dev, "unable to ioremap\n");
		return -ENXIO;
	}

	dev_err(dev, "Regs maps to %p(%08x)\n", rts_g2d_device->regmap_base, res->start);

	/* enable the clock */
	rts_g2d_device->clk = devm_clk_get(dev, "ge_ck");
	if (IS_ERR(rts_g2d_device->clk)) {
		dev_err(dev, "failed to get clock\n");
		return PTR_ERR(rts_g2d_device->clk);
	}

	ret = clk_prepare_enable(rts_g2d_device->clk);
	if (ret) {
		dev_err(dev, "clock prepare failed.\n");
		return PTR_ERR(rts_g2d_device->clk);
	}

	/* turn on mem of g2d controller */
	rts_g2d_device->sysmem = devm_reset_control_get(&pdev->dev,
			"rtsg2d-sysmem-up");
	if (IS_ERR(rts_g2d_device->sysmem)) {
		dev_err(dev, "no top level reset found.\n");
		return PTR_ERR(rts_g2d_device->sysmem);
	}

	reset_control_deassert(rts_g2d_device->sysmem);
	mdelay(100);

	/* reset SD controller */
	rts_g2d_device->rst = devm_reset_control_get(&pdev->dev, "rst");
	if (IS_ERR(rts_g2d_device->rst)) {
		dev_err(dev, "no top level reset found.\n");
		return PTR_ERR(rts_g2d_device->rst);
	}

	reset_control_reset(rts_g2d_device->rst);
	mdelay(100);

	rts_g2d_device->irq = platform_get_irq(pdev, 0);
	if (rts_g2d_device->irq < 0) {
		dev_err(dev, "no IRQ defined\n");
		return -ENODEV;
	}

	ret = devm_request_irq(dev, rts_g2d_device->irq, rts_g2d_irq_handler,
			  0, dev_name(dev), rts_g2d_device);

	if (ret < 0) {
		dev_err(dev, "devm_request_irq failed\n");
		return ret;
	}

	dev_info(dev, "using IRQ channel %d\n", rts_g2d_device->irq);

	/* alloc dma */
	rts_g2d_device->cmd_buf
	    = dma_alloc_coherent(dev, RTS_G2D_COMMAND_BUFFER_LENGTH,
				 &rts_g2d_device->cmd_buf_phy, GFP_KERNEL);
	if (!rts_g2d_device->cmd_buf) {
		dev_err(dev, "Unable to allocate dma shared buffer\n");
		return -ENOMEM;
	}

	DBG("buffer(noncache) = %p(%08x)\n", rts_g2d_device->cmd_buf,
		(unsigned int)rts_g2d_device->cmd_buf_phy);

	rts_g2d_device->misc_dev = fellow_misc;

	/* register misc device */
	ret = misc_register(&rts_g2d_device->misc_dev);
	if (ret < 0) {
		dev_err(dev, "misc_register() for minor %d failed\n",
			MISCDEV_MINOR);
		goto err_free_dma;
	}

	/* create device files */
	ret = sysfs_create_group(&pdev->dev.kobj, &g2d_attribute_group);
	if (ret)
		goto err_free_dma;

	rts_g2d_device->size = RTS_G2D_COMMAND_BUFFER_LENGTH;
	rts_g2d_device->cmd_base = (__u32) rts_g2d_device->cmd_buf;
	rts_g2d_device->cmd_write_ptr = rts_g2d_device->cmd_base;
	rts_g2d_device->cmd_read_ptr = rts_g2d_device->cmd_base;

	rts_g2d_device->cmd_limit =
		(__u32)(rts_g2d_device->cmd_buf) + rts_g2d_device->size;

	INFO("cmd_buf:%p, size:0x%x, cmd_base:0x%x, cmd_limit:0x%x\n",
		rts_g2d_device->cmd_buf, rts_g2d_device->size,
		rts_g2d_device->cmd_base, rts_g2d_device->cmd_limit);

	platform_set_drvdata(pdev, rts_g2d_device);

	rts_g2d_device->cmd_stats = 0;
	rts_g2d_device->cmd_count = 0;

	rts_g2d_init(rts_g2d_device);
	init_completion(&rts_g2d_device->se_complete);

	return 0;

err_free_dma:
	dma_free_coherent(dev, RTS_G2D_COMMAND_BUFFER_LENGTH,
			rts_g2d_device->cmd_buf, rts_g2d_device->cmd_buf_phy);

	misc_deregister(&rts_g2d_device->misc_dev);

	return ret;
}

static int rts_g2d_remove(struct platform_device *pdev)
{
	struct rts_g2d_dev *priv;
	struct device *dev;
	int i;

	priv = platform_get_drvdata(pdev);
	dev = &pdev->dev;

	dev_info(dev, "se clean module misc minor = %d\n", MISCDEV_MINOR);

	sysfs_remove_group(&dev->kobj, &g2d_attribute_group);

	misc_deregister(&priv->misc_dev);

	/* free allocated mem slots */
	for (i = 0; i < MAX_SLOTS; i++) {
		if (mem_slots[i].mem_virt)
			dma_free_coherent(dev, mem_slots[i].mem_len,
				mem_slots[i].mem_virt, mem_slots[i].mem_phy);
	}

	/* free cmd buffer */
	dma_free_coherent(dev, RTS_G2D_COMMAND_BUFFER_LENGTH, priv->cmd_buf, priv->cmd_buf_phy);

	devm_free_irq(dev, priv->irq, priv);

	reset_control_assert(rts_g2d_device->rst);
	reset_control_assert(rts_g2d_device->sysmem);
	clk_disable_unprepare(rts_g2d_device->clk);
	INFO("Driver unloaded succesfully.\n");
	return 0;
}

static int rts_g2d_suspend(struct platform_device *pdev, pm_message_t state)
{
	clk_disable_unprepare(rts_g2d_device->clk);
	INFO("rts_g2d_suspend succesfully.\n");

	return 0;
}

static int rts_g2d_resume(struct platform_device *pdev)
{
	INFO("%s\n", __func__);
	clk_prepare_enable(rts_g2d_device->clk);
	INFO("rts_g2d_resume succesfully.\n");

	return 0;
}

#ifdef CONFIG_HAS_EARLYSUSPEND
void rts_g2d_early_suspend(struct early_suspend *h)
{

}

void rts_g2d_late_resume(struct early_suspend *h)
{

}

static struct early_suspend rts_g2d_early_suspend_handler = {
	.level = EARLY_SUSPEND_LEVEL_DISABLE_FB,
	.suspend = rts_g2d_early_suspend,
	.resume = rts_g2d_late_resume,
};
#endif

static const struct of_device_id rts_g2d_dt_ids[] = {
	{ .compatible = "realtek,rts3915-g2d"},
	{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, rts_g2d_dt_ids);

static struct platform_driver rts_g2d_driver = {
	.probe = rts_g2d_probe,
	.remove = rts_g2d_remove,
#ifndef CONFIG_HAS_EARLYSUSPEND
	.suspend        = rts_g2d_suspend,
	.resume         = rts_g2d_resume,
#else
	.suspend        = NULL,
	.resume         = NULL,
#endif
	.driver = {
		.owner		= THIS_MODULE,
		.name		= DRV_NAME,
		.of_match_table = of_match_ptr(rts_g2d_dt_ids),
	},
};

module_platform_driver(rts_g2d_driver);

MODULE_AUTHOR("shawn_chen@realsil.com.cn");
MODULE_DESCRIPTION("RTS3915 rts_g2d driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rts_g2d");

