/* * Device driver for the VGA encrypted-domain accelerator
 *
 * A Platform device implemented using the misc subsystem

 * Lanxiang Hu
 *
 * References:
 * Linux source: Documentation/driver-model/platform.txt
 *               drivers/misc/arm-charlcd.c
 * http://www.linuxforu.com/tag/linux-device-drivers/
 * http://free-electrons.com/docs/
 *
 * "make" to build
 * insmod encrypted_domain.ko
 *
 * Check code style with
 * checkpatch.pl --file --no-tree encrypted_domain.c
 */

#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 "encrypted_domain.h"

#define DRIVER_NAME "encrypted_domain"

/* Device registers */
#define OP_TYPE(x) ((x))
#define DATA_TYPE(x) ((x+4))
#define WIDTH(x) ((x)+8)
#define LENGTH(x) ((x)+12)
#define INPUT(x) ((x)+16)
#define OUTPUT_0(x) ((x)+20)
#define OUTPUT_1(x) ((x)+24)
#define OUTPUT_2(x) ((x)+28)
#define OUTPUT_3(x) ((x)+32)
#define OUTPUT_4(x) ((x)+36)
#define OUTPUT_5(x) ((x)+40)
#define OUTPUT_6(x) ((x)+44)
#define OUTPUT_8(x) ((x)+48)
#define OUTPUT_9(x) ((x)+52)
#define OUTPUT_10(x) ((x)+56)
#define OUTPUT_11(x) ((x)+60)
#define OUTPUT_12(x) ((x)+64)
#define OUTPUT_13(x) ((x)+68)
#define OUTPUT_14(x) ((x)+72)
#define OUTPUT_15(x) ((x)+76)
#define OUTPUT_LENGTH(x) ((x)+80)
#define OUTPUT_WIDTH(x) ((x)+84)
#define OUTPUT_DONE(x) ((x)+88)
#define OUTPUT_RESET(x) ((x)+92)
#define OUTPUT_LOADED(x) ((x)+96)
#define W(x) ((x)+100)


/*
 * Information about our device
 */
struct encrypted_domain_dev {
	struct resource res; /* Resource: our registers */
	void __iomem *virtbase; /* Where registers can be accessed in memory */
		encrypted_domain_loading_t load_info;
		encrypted_domain_status_t status_info;
		encrypted_domain_output_t output_info;
} dev;

/*
 * Write segments of a single digit
 * Assumes digit is in range and the device information has been set up
 */
static void write_op_type(encrypted_domain_loading_t load_info)
{
	iowrite8(load_info->input, OP_TYPE(dev.virtbase) );
	dev.load_info = *load_info;
}

static void write_op_type(encrypted_domain_loading_t load_info)
{
	iowrite8(load_info->data_type, DATA_TYPE(dev.virtbase) );
	dev.load_info = *load_info;
}

static void write_width(encrypted_domain_loading_t load_info)
{
	iowrite8(load_info->input, WIDTH(dev.virtbase) );
	dev.load_info = *load_info;
}

static void write_length(encrypted_domain_loading_t load_info)
{
	iowrite8(load_info->input, LENGTH(dev.virtbase) );
	dev.load_info = *load_info;
}

static void write_ell(encrypted_domain_loading_t load_info)
{
	iowrite8(load_info->input, ELL(dev.virtbase) );
	dev.load_info = *load_info;
}

static void write_w(encrypted_domain_loading_t load_info)
{
	iowrite32(load_info->w, INPUT(dev.virtbase) );
	dev.load_info = *load_info;
}

static void write_input(encrypted_domain_loading_t load_info)
{
	iowrite32(load_info->input, INPUT(dev.virtbase) );
	dev.load_info = *load_info;
}

static void set_reset(encrypted_domain_status_t status_info)
{
	iowrite8(status_info->reset, RESET(dev.virtbase) );
	dev.status_info = *status_info;
}

static void set_all_loaded(encrypted_domain_status_t status_info)
{
	iowrite8(status_info->all_loaded, RESET(dev.virtbase) );
	dev.status_info = *status_info;
}

static void read_output_0(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_0(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_1(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_1(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_2(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_2(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_3(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_3(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_4(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_4(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_5(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_5(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_6(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_6(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_7(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_7(dev.virtbase) );
	output_info = dev.output_info;
}


static void read_output_8(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_8(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_9(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_9(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_10(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_10(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_11(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_11(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_12(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_12(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_13(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_13(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_14(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_14(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_output_15(encrypted_domain_output_t output_info)
{	
	ioread32(OUTPUT_15(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_length(encrypted_domain_output_t output_info)
{	
	ioread8(OUTPUT_LENGTH(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_width(encrypted_domain_output_t output_info)
{	
	ioread8(OUTPUT_WIDTH(dev.virtbase) );
	output_info = dev.output_info;
}

static void read_done(encrypted_domain_output_t output_info)
{	
	ioread8(OUTPUT_DONE(dev.virtbase) );
	output_info = dev.output_info;
}

/*
 * Handle ioctl() calls from userspace:
 * Read or write the segments on single digits.
 * Note extensive error checking of arguments
 */
static long encrypted_domain_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
	encrypted_domain_arg_t ksa;

	switch (cmd) {
	case ENCRYPTED_DOMAIN_WRITE:
		if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		loading_input(&ksa.load_info);
		break;
	
	case ENCRYPTED_DOMAIN_WRITE_OP_TYPE:
		if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		write_op_type(&ksa.load_info);
		break;
	
	case ENCRYPTED_DOMAIN_WRITE_DATA_TYPE:
		if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		write_data_type(&ksa.load_info);
		break;
	
	case ENCRYPTED_DOMAIN_WRITE_WIDTH:
		if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		write_width(&ksa.load_info);
		break;
	
	case ENCRYPTED_DOMAIN_WRITE_LENGTH:
		if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		write_length(&ksa.load_info);
		break;


	case ENCRYPTED_DOMAIN_READ_0:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_0(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_1:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_1(&ksa.output_info);
		break;

	case ENCRYPTED_DOMAIN_READ_2:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_2(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_3:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_3(&ksa.output_info);
		break;

	case ENCRYPTED_DOMAIN_READ_4:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_4(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_5:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_5(&ksa.output_info);
		break;

	case ENCRYPTED_DOMAIN_READ_6:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_6(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_7:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_7(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_8:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_8(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_9:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_9(&ksa.output_info);
		break;

	case ENCRYPTED_DOMAIN_READ_10:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_10(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_11:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_11(&ksa.output_info);
		break;

	case ENCRYPTED_DOMAIN_READ_12:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_12(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_13:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_13(&ksa.output_info);
		break;

	case ENCRYPTED_DOMAIN_READ_14:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_14(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_15:
		if (copy_to_user((encrypted_domain_arg_t *) arg, &ska,
				 sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		read_output_15(&ksa.output_info);
		break;
	
		case ENCRYPTED_DOMAIN_READ_WIDTH:
		if (copy_to_user((key_switching_arg_t *) arg, &ska,
				 sizeof(key_switching_arg_t)))
			return -EACCES;
		read_width(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_LENGTH:
		if (copy_to_user((key_switching_arg_t *) arg, &ska,
				 sizeof(key_switching_arg_t)))
			return -EACCES;
		read_length(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_READ_DONE:
		if (copy_to_user((key_switching_arg_t *) arg, &ska,
				 sizeof(key_switching_arg_t)))
			return -EACCES;
		read_done(&ksa.output_info);
		break;
	
	case ENCRYPTED_DOMAIN_WRITE_RESET:
	  	if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		set_reset(&ksa.status_info);
		break;
	
	case ENCRYPTED_DOMAIN_WRITE_LOADED:
	  	if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		set_all_loaded(&ksa.status_info);
		break;
	
	case ENCRYPTED_DOMAIN_WRITE_W:
	  	if (copy_from_user(&ksa, (encrypted_domain_arg_t *) arg,
				   sizeof(encrypted_domain_arg_t)))
			return -EACCES;
		write_w(&ksa.status_info);
		break;
	
	default:
		return -EINVAL;
	}

	return 0;
}

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

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

/*
 * Initialization code: get resources (registers) and have the accelerator ready
 */
static int __init encrypted_domain_probe(struct platform_device *pdev)
{	
	encrypted_domain_status_t load_reset = {0x1, 0x0, 0x0};
	int ret;

	/* Register ourselves as a misc device: creates /dev/encrypted_domain */
	ret = misc_register(&encrypted_domain_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;
	}
        
	/* reset */
    loading_input(&load_reset);

	return 0;

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

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

/* Which "compatible" string(s) to search for in the Device Tree */
#ifdef CONFIG_OF
static const struct of_device_id encrypted_domain_of_match[] = {
	{ .compatible = "csee4840-sonny-hu,encrypted_domain_1" },
	{},
};
MODULE_DEVICE_TABLE(of, encrypted_domain_of_match);
#endif

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

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

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

module_init(encrypted_domain_init);
module_exit(encrypted_domain_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lanxiang Hu");
MODULE_DESCRIPTION("encrypted-domain accelerator");
