/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License 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 https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/

/*-----------------------------------------------------------------------------
 *
 * $Author: dtvbm11 $
 * $Date: 2015/11/10 $
 * $RCSfile: fops_jpg.c,v $
 * $Revision: #1 $
 *
 *---------------------------------------------------------------------------*/

/** @file fops_fbm.c
 *  Module interface for FBM memory
 */

/* How to use these interface to get FBM information in user space:
 *  
 * - To use this, you must add /dev/fbm_xxx to rootfs.
 *   In turnkey, modify chiling/rootfs/common/misc/dev4_2.txt to add line for 
 *   your FBM pool, the major is 251, minor is (64 + pool type number)
 *      dev/fbm_dmx             c   644     0     0  251   64 
 *
 *   In Android, modify init script to do this.
 *  !!!!!NOTE!!!!! Please always add new pool type at last posistion.
 *
 * - Add '#include <linux/mt53xx_fbm.h>' if you need ioctl defines.
 *   NEVER copy/paste defines to your own source code in all case.
 *
 * - How to properly and fully map a FBM pool to user space. 
 *   Even when it is not page aligned.  Pseudo code:
 *
 *    #ifndef PAGE_SIZE
 *    #define PAGE_SIZE 4096
 *    #endif
 *
 *    // Create a read-only map
 *    unsigned int fbm_base, fbm_size, map_size;
 *    void *map_ptr, *fbm_ptr;
 *
 *    ioctl(fd, FBM_IOCTL_GET_BASE, &fbm_base);
 *    ioctl(fd, FBM_IOCTL_GET_SIZE, &fbm_size);
 *    map_size = ((fbm_base+fbm_size+PAGE_SIZE-1) & (~(PAGE_SIZE-1))) - (fbm_base & (~(PAGE_SIZE-1)));
 *    map_ptr = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0);
 *    fbm_ptr = (char*)map_ptr + (fbm_base & (PAGE_SIZE-1));
 *
 *
 *    // Unmap previous mapped address
 *    munmap(map_ptr, map_size);
 *
 */

//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------

#include "ionodes.h"
#include "x_linux.h"
#include "fbm_drvif.h"
#include "x_assert.h"
#include "x_hal_5381.h"
#include <linux/mt53xx_fbm.h>
#include <linux/version.h>
#include <linux/blkdev.h>


static int fbm_mmap(struct file *file, struct vm_area_struct *vma)
{
	unsigned long off, start;
	u32 len;
	dev_t dev;
	FBM_POOL_T *prFbmPool;
	unsigned int poolNum;

	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
		return -EINVAL;

	dev = file->f_dentry->d_inode->i_rdev;

	off = vma->vm_pgoff << PAGE_SHIFT;

	poolNum = MINOR(dev) - IO_NODE_MINOR_FBM;

	// Special handling for total/total2
	// They are fixed to last 2 entries.
	if (poolNum == IO_NODE_NUMBER_FBM - 2)
			poolNum = FBM_POOL_TYPE_TOTAL;
	if (poolNum == IO_NODE_NUMBER_FBM - 1)
			poolNum = FBM_POOL_TYPE_TOTAL2;

	if (poolNum >= FBM_POOL_TYPE_NS)
			return -EINVAL;

	prFbmPool = FBM_GetPoolInfo((UINT8)poolNum);

	start = prFbmPool->u4Addr & PAGE_MASK;
	len = PAGE_ALIGN(prFbmPool->u4Addr + prFbmPool->u4Size - start);

	if ((vma->vm_end - vma->vm_start + (u64)off) > len)
	{
		return -EINVAL;
	}
	off += start;
	vma->vm_pgoff = off >> PAGE_SHIFT;

	/* This is an IO map - tell maydump to skip this VMA */
	vma->vm_flags |= VM_IO;

	/*
	 * Don't alter the page protection flags; we want to keep the area
	 * cached for better performance.  This does mean that we may miss
	 * some updates to the screen occasionally, but process switches
	 * should cause the caches and buffers to be flushed often enough.
	 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)	
	if (io_remap_pfn_range(vma, vma->vm_start, (off >> PAGE_SHIFT),
				vma->vm_end - vma->vm_start,
				vma->vm_page_prot))
#else
	if (io_remap_page_range(vma, vma->vm_start, off,
		vma->vm_end - vma->vm_start,
		vma->vm_page_prot))
#endif   
	{
		return -EAGAIN;
	}

	return 0;
}

static inline unsigned long vaddr_to_phys(void const *x)
{
	unsigned long flags, i;
	local_irq_save(flags);
	asm volatile (
	    "  mcr p15, 0, %1, c7, c8, 0\n"
	    "  mrc p15, 0, %0, c7, c4, 0\n"
	    : "=r" (i) : "r" (x) : "cc");
	local_irq_restore(flags);
	if (i & 1)
		return 0;
	return (i & PAGE_MASK) | ((unsigned long)(x) & ~PAGE_MASK);
}

static long
fbm_gen_ioctl(dev_t dev, unsigned int cmd, unsigned long arg)
{
	UINT32 start, len, start_vm;
	FBM_POOL_T *prFbmPool;
	unsigned int poolNum;
	INT32 i4Ret=0;

	poolNum = MINOR(dev) - IO_NODE_MINOR_FBM;

	// Special handling for total/total2
	// They are fixed to last 2 entries.
	if (poolNum == IO_NODE_NUMBER_FBM - 2)
		poolNum = FBM_POOL_TYPE_TOTAL;
	if (poolNum == IO_NODE_NUMBER_FBM - 1)
		poolNum = FBM_POOL_TYPE_TOTAL2;

	if (poolNum >= FBM_POOL_TYPE_NS)
		return -EINVAL;

	prFbmPool = FBM_GetPoolInfo((UINT8)poolNum);

	start = prFbmPool->u4Addr & PAGE_MASK;

	switch (cmd) 
	{
	    case FBM_CMD_GET_BASE:                       // For legacy use
	    case FBM_IOCTL_GET_BASE:
		if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		if (copy_to_user((void __user *)arg, &start, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		break;

	    case FBM_CMD_GET_SIZE:                   // For legacy use
	    case FBM_IOCTL_GET_SIZE:
		if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		len = PAGE_ALIGN(prFbmPool->u4Addr + prFbmPool->u4Size - start);
		if (copy_to_user((void __user *)arg, &len, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		break;

	    case FBM_IOCTL_GET_BASE_VM:
		if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		start_vm = VIRTUAL(start);
		if (copy_to_user((void __user *)arg, &start_vm, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		break;

	    case FBM_IOCTL_PHYSICAL:
		if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		if (copy_from_user(&start, (void __user *)arg, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}

		// Translate to physical address, by using ARM register.
		start_vm = vaddr_to_phys((void*)start);

		if (copy_to_user((void __user *)arg, &start_vm, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
	    	break;

	    case FBM_IOCTL_FBM_VIRTUAL:
		if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
		if (copy_from_user(&start, (void __user *)arg, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}

		start_vm = VIRTUAL(start);

		if (copy_to_user((void __user *)arg, &start_vm, sizeof(UINT32)))
		{
		    i4Ret = -EFAULT;
		    goto _FBM_IOCTL_EXIT;
		}
	    	break;

	    default:
		i4Ret = -EINVAL;
	}

_FBM_IOCTL_EXIT:
	return i4Ret;
}

static long 
fbm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	return fbm_gen_ioctl(file->f_dentry->d_inode->i_rdev, cmd, arg);
}

struct file_operations fbm_fops = {
	.owner   = THIS_MODULE,
	.mmap  = fbm_mmap,
	//.ioctl = fbm_ioctl,
	.unlocked_ioctl = fbm_ioctl,
};


//////////////////////////////////////////////////////////////////////
///	FBM block device support.
//////////////////////////////////////////////////////////////////////

struct fbm_disk
{
	unsigned int poolNum;
	char *pBase;
	UINT32 u4Len;
	struct gendisk *disk;
};

static DEFINE_MUTEX(fbm_probe_mutex);
static DEFINE_SPINLOCK(fbm_blk_lock);
static struct fbm_disk *fbm_disks[IO_NODE_NUMBER_FBM];

static void
do_fbm_blk_request(struct request_queue *q)
{
	struct request *req;

	req = blk_fetch_request(q);
	while (req) {
		unsigned long start = blk_rq_pos(req) << 9;
		unsigned long len  = blk_rq_cur_bytes(req);
		int err = 0;
		struct gendisk *disk = req->rq_disk;
		struct fbm_disk *diskInfo = (struct fbm_disk*)disk->private_data;

		if (start + len > diskInfo->u4Len) {
			err = -EIO;
			goto done;
		}
		if (rq_data_dir(req) == READ)
			memcpy(req->buffer, diskInfo->pBase+start, len);
		else
			memcpy(diskInfo->pBase+start, req->buffer, len);
	done:
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);
	}
}

static int
fbm_blk_ioctl (struct block_device *bdev, fmode_t mode, unsigned cmd, unsigned long arg)
{
	return fbm_gen_ioctl(bdev->bd_dev, cmd, arg);
}

static const struct block_device_operations fbm_blk_fops = {
	.owner =		THIS_MODULE,
	.ioctl =		fbm_blk_ioctl,
};

/* Create disk on demand. So we won't create lots of disk for un-used devices. */
static struct kobject *
fbm_blk_probe(dev_t dev, int *part, void *data)
{
	UINT32 start, len;
	FBM_POOL_T *prFbmPool, *prTotalPool;
	unsigned int poolNum;
	struct fbm_disk *diskInfo;
	struct gendisk *disk;
	struct kobject *kobj;
	struct request_queue *queue;

	poolNum = MINOR(dev) - IO_NODE_MINOR_FBM;

	// Special handling for total/total2
	// They are fixed to last 2 entries.
	if (poolNum == IO_NODE_NUMBER_FBM - 2)
		poolNum = FBM_POOL_TYPE_TOTAL;
	if (poolNum == IO_NODE_NUMBER_FBM - 1)
		poolNum = FBM_POOL_TYPE_TOTAL2;

	if (poolNum >= FBM_POOL_TYPE_NS)
		return ERR_PTR(-EINVAL);

	mutex_lock(&fbm_probe_mutex);
	
	diskInfo = fbm_disks[poolNum];
	if (!diskInfo)
	{
		diskInfo = kmalloc(GFP_KERNEL, sizeof(struct fbm_disk));
		if (!diskInfo)
			goto out_info;
		
		disk = alloc_disk(1);
		if (!disk)
			goto out_disk;

		queue = blk_init_queue(do_fbm_blk_request, &fbm_blk_lock);
		if (!queue)
			goto out_queue;
		blk_queue_max_hw_sectors(queue, 1024);
		blk_queue_bounce_limit(queue, BLK_BOUNCE_ANY);

		prFbmPool = FBM_GetPoolInfo((UINT8)poolNum);

		start = prFbmPool->u4Addr & PAGE_MASK;
		len = PAGE_ALIGN(prFbmPool->u4Addr + prFbmPool->u4Size - start);

		// Map FBM area if not already mapped.
		// Assume FBM in channel 0 is already mapped, and all others are not.
		prTotalPool = FBM_GetPoolInfo(FBM_POOL_TYPE_TOTAL);
		if (start >= prTotalPool->u4Addr && start < (prTotalPool->u4Addr + prTotalPool->u4Size))
		{
			diskInfo->pBase = (char*)VIRTUAL(start);
		}
		else
		{
			diskInfo->pBase = ioremap_cached(start, len);
		}
		if (!diskInfo->pBase)
			goto out_ioremap;

		disk->major = IO_NODE_MAJOR_FBM;
		disk->first_minor = MINOR(dev);
		disk->fops = &fbm_blk_fops;
		disk->private_data = diskInfo;
		snprintf(disk->disk_name, sizeof(disk->disk_name), "fbmblk%d", MINOR(dev));
		disk->queue = queue;
		set_capacity(disk, len / 512);
		add_disk(disk);

		diskInfo->poolNum = poolNum;
		diskInfo->u4Len = len;
		diskInfo->disk = disk;
		fbm_disks[poolNum] = diskInfo;
	}

	*part = 0;
	kobj = diskInfo ? get_disk(diskInfo->disk) : ERR_PTR(-ENOMEM);
	mutex_unlock(&fbm_probe_mutex);

	return kobj;

out_ioremap:
	blk_cleanup_queue(queue);
out_queue:
	put_disk(disk);
out_disk:
	kfree(diskInfo);
out_info:
	mutex_unlock(&fbm_probe_mutex);
	return ERR_PTR(-ENOMEM);
}

// Register FBM block device.
void __init fbm_blkdev_init(void)
{
	// Assert check to make sure FBM pool is added to last
	ASSERT(FBM_POOL_TYPE_FEEDER3 == 60 && "FBM Pool type error, please add new pool type to last entries");

	if (register_blkdev(IO_NODE_MAJOR_FBM, "fbm"))
		return;

	blk_register_region(MKDEV(IO_NODE_MAJOR_FBM, IO_NODE_MINOR_FBM), IO_NODE_NUMBER_FBM,
		THIS_MODULE, fbm_blk_probe, NULL, NULL);
}

