/*
Adapted from boiler plate code provided by Prof.Stephen Edwards
*/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include "ccl_dd.h"

#define DRIVER_NAME "ccl"

/*
 * Information about the device
 */
struct ccl_dev {
	struct resource res; /* Resource: our registers */
	void __iomem *virtbase; /* Where registers can be accessed in memory */
	unsigned char *pix_data;
	unsigned int pix_count;
} dev;

/*
 * Transmit a row of pixel data to the ccl device
 */
static void write_row(unsigned char* pix_data, unsigned int pix_count)
{
	int i;
	unsigned int temp;
	
	/**Group and format pixel data in little endian format before sending it to the RAM**/
	for(i = 0; i < pix_count; i++)
	{
		if(i%4 == 0) {
			temp = 0;
		}
		temp = (temp << 8) | *(pix_data +  4*((int)i/4) + (3-(i%4)));
		if(i%4 == 3)  {
			iowrite32(temp, dev.virtbase + (i-3));
		}
	}
}

/*
 * Read a row of pixel data from the ccl device
 */
static void read_row(unsigned char* pix_data, unsigned int pix_count)
{
	int i;
	unsigned int temp = 0;

	/**Unpack pixel data in little endian format and update the byte array in the image**/
	for(i = 0; i < pix_count; i++)
	{
		if(i%4 == 0) {
			temp = ioread32(dev.virtbase + i);
		}
		*(pix_data + 4*((int)i/4) + ((i%4))) = temp & 0xFF;
		temp = temp >> 8;
                if(i%4 == 3) {
			temp = 0;
		}
	}
}

/*
 * Send commands and status notifications to the ccl device
 */
static void write_reg(unsigned int address, unsigned int val)
{
	iowrite32(val, dev.virtbase + 4*address);
}

/*
 * Poll statu registers from the ccl device to control program flow
 */
static void poll_reg(unsigned int address, unsigned int *val)
{
	*val = ioread32(dev.virtbase + 4*address);
}

/*
 * Handle ioctl() calls from userspace:
 * Read or write the pixel data.
 * Note extensive error checking of arguments
 */
static long ccl_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
	img_row_arg_t img_row;
	reg_arg_t ra;

	switch (cmd) {	
	case CCL_WRITE_DATA:
		if (copy_from_user(&img_row, (img_row_arg_t *) arg,
				   sizeof(img_row_arg_t)))
			return -EACCES;
		write_row(img_row.pix_data, img_row.pix_count);
		break;

	case CCL_READ_DATA:
		if (copy_from_user(&img_row, (img_row_arg_t *) arg,
				   sizeof(img_row_arg_t)))
			return -EACCES;
	
		read_row(img_row.pix_data, img_row.pix_count);
		
		if (copy_to_user((img_row_arg_t *) arg, &img_row,
				 sizeof(img_row_arg_t)))
			return -EACCES;
		break;

	case CCL_WRITE_REG:
		if (copy_from_user(&ra, (reg_arg_t *) arg,
				   sizeof(reg_arg_t)))
			return -EACCES;

		write_reg(ra.address, ra.reg_data);
		break;

	case CCL_POLL_REG:
		if (copy_from_user(&ra, (reg_arg_t *) arg,
				   sizeof(reg_arg_t)))
			return -EACCES;

		poll_reg(ra.address, &ra.reg_data);
		
		if (copy_to_user((reg_arg_t *) arg, &ra,
				 sizeof(reg_arg_t)))
			return -EACCES;
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

/* The operations our device knows how to do */
static const struct file_operations ccl_fops = {
	.owner		= THIS_MODULE,
	.unlocked_ioctl = ccl_ioctl,
};

/* Information about our device for the "misc" framework -- like a char dev */
static struct miscdevice ccl_misc_device = {
	.minor		= MISC_DYNAMIC_MINOR,
	.name		= DRIVER_NAME,
	.fops		= &ccl_fops,
};

/*
 * Initialization code: get resources (registers) and display
 * a welcome message
 */
static int __init ccl_probe(struct platform_device *pdev)
{
	int ret;

    printk(KERN_INFO "ccl_dd: In init");

	/* Register ourselves as a misc device: creates /dev/ccl */
	ret = misc_register(&ccl_misc_device);

	/* Get the address of our registers from the device tree */
	ret = of_address_to_resource(pdev->dev.of_node, 0, &dev.res);
	if (ret) {
		ret = -ENOENT;
		goto out_deregister;
	}

	/* Make sure we can use these registers */
	if (request_mem_region(dev.res.start, resource_size(&dev.res),
			       DRIVER_NAME) == NULL) {
		ret = -EBUSY;
		goto out_deregister;
	}

	/* Arrange access to our registers */
	dev.virtbase = of_iomap(pdev->dev.of_node, 0);
	if (dev.virtbase == NULL) {
		ret = -ENOMEM;
		goto out_release_mem_region;
	}

	///* Display a welcome message */
	//write_digit(cx, cy);
        
	return 0;

out_release_mem_region:
	release_mem_region(dev.res.start, resource_size(&dev.res));
out_deregister:
	misc_deregister(&ccl_misc_device);
	return ret;
}

/* Clean-up code: release resources */
static int ccl_remove(struct platform_device *pdev)
{
	iounmap(dev.virtbase);
	release_mem_region(dev.res.start, resource_size(&dev.res));
	misc_deregister(&ccl_misc_device);
	return 0;
}

/* Which "compatible" string(s) to search for in the Device Tree */
#ifdef CONFIG_OF
static const struct of_device_id ccl_of_match[] = {
	{ .compatible = "altr,ccl" },
	{},
};
MODULE_DEVICE_TABLE(of, ccl_of_match);
#endif

/* Information for registering ourselves as a "platform" driver */
static struct platform_driver ccl_driver = {
	.driver	= {
		.name	= DRIVER_NAME,
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(ccl_of_match),
	},
	.remove	= __exit_p(ccl_remove),
};

/* Called when the module is loaded: set things up */
static int __init ccl_init(void)
{
	pr_info(DRIVER_NAME ": init\n");
	return platform_driver_probe(&ccl_driver, ccl_probe);
}

/* Called when the module is unloaded: release resources */
static void __exit ccl_exit(void)
{
	platform_driver_unregister(&ccl_driver);
	pr_info(DRIVER_NAME ": exit\n");
}

module_init(ccl_init);
module_exit(ccl_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Avinash, Manushree, Jerry, Columbia University");
MODULE_DESCRIPTION("Connected Component Labeling");
