 
/* * Device driver for Viewtube Hardware Module
 *
 * A Platform device implemented using the misc subsystem
 *
 * Adapted from Embedded Systems Lab 3 by Stephen A. Edwards
 * Columbia University
 *
 * 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 viewtube_kmod.ko
 *
 * Check code style with
 * checkpatch.pl --file --no-tree viewtube_kmod.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/types.h>
#include <linux/uaccess.h>
#include "viewtube_kmod.h"

#include <linux/types.h>
#include <linux/ioctl.h>

#define DRIVER_NAME "viewtube"
#define VIEWTUBE_BACKGROUND_X_AXIS 0
#define VIEWTUBE_BACKGROUND_Y_AXIS 1

/* Device registers */
#define BACKGROUND_POSITION(x) (x)
#define SPRITE_DATA_WRITE(x)   (x+4)
#define SPRITE_POS_WRITE(x)    (x+8)

/*
 * Information about our device
 */
struct viewtube_dev {
	struct resource res; /* Resource: our registers */
	void __iomem *virtbase; /* Where registers can be accessed in memory */
    unsigned short background_x_pos;
    unsigned short background_y_pos;
    unsigned char  background_transition_bounce;
} dev;


static void write_sprite_to_table(viewtube_sprite_t * sprite)
{
	u32 val = 0;

	val |= ((sprite->table_index << 24) & 0xFF000000);
	// set table offset value

	val |= ((sprite->type << 23) & 0x00800000); 
	// set table type; mask bit at offset 23


	if(SPRITE_TYPE_COLOR == sprite->type)
	{

		val |= ((sprite->obj.color.sprite_num << 16) & 0x001F0000); 
		//set new sprite #; mask 5 bits at offsets 20:16

		val |= ((sprite->obj.color.width << 8) & 0x00001F00); 
		// set width; mask 5 bits at offsets 12:8

		val |= ((sprite->obj.color.height) & 0x0000001F); 
		// set height; mask 5 bits at offsets 4:0

	} else { //mono
		val |= ((sprite->obj.color.sprite_num << 16) & 0x007F0000); 
		//set new sprite #; mask 5 bits at offsets 20:16

		val |= ((sprite->obj.mono.red << 8) & 0x00000F00); 
		// set red; mask 4 bits at offsets 11:8
		
		val |= ((sprite->obj.mono.green << 4) & 0x000000F0); 
		// set green; mask 4 bits at offsets 7:4

		val |= ((sprite->obj.mono.blue) & 0x0000000F); 
		// set blue; mask 4 bits at offsets 3:0
	}
	iowrite32(val, SPRITE_DATA_WRITE(dev.virtbase) );
}

static void write_new_sprite_pos(viewtube_sprite_t * sprite)
{
	u32 val = 0;

	val |= ((sprite->table_index << 24) & 0xFF000000);
	// set table offset value

	val |= ((sprite->jump<< 23) & 0x00800000); 
	// set whether to jump to position or slide; mask bit at offset 23

	val |= ((sprite->new_y << 11 ) & 0x003FF800); 
	// set y_pos; mask 11 bits at offset 21:11

	val |= ((sprite->new_x ) & 0x000007FF); 
	// set x_pos; mask 11 bits at offset 10:0

	iowrite32(val, SPRITE_POS_WRITE(dev.virtbase) );
}



static void write_background_position(viewtube_background_window_position_t *background, unsigned char axis)
{
  unsigned short coordinate = 0;
  u32 val = 0;
  if (VIEWTUBE_BACKGROUND_X_AXIS == axis)
  {
      dev.background_x_pos = background->coordinate_x;
      coordinate = background->coordinate_x;
  } else {
      dev.background_y_pos = background->coordinate_y;
      coordinate = background->coordinate_y;
  }
  dev.background_transition_bounce = background->bounce;
  
  val = val + ( ( axis << 24) & 0xFF000000) \
            + ( ( background->bounce << 16) & 0x00FF0000) \
            + ( ( coordinate ) & 0x007FF); 
            // 0x7FF is mask for max of 11 bit string.
	iowrite32(val, BACKGROUND_POSITION(dev.virtbase) );
}

static void get_background_position_from_device(void)
{
  u32 positions = ioread32(BACKGROUND_POSITION(dev.virtbase) );
  dev.background_x_pos = (u16) ( positions >> 16 );
  dev.background_y_pos = (u16) (positions & 0x0000FFFF);
}



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

	switch (cmd) {
        case VIEWTUBE_SET_BACKGROUND_POS_X:
            if (copy_from_user(&vla, (viewtube_arg_t *) arg,
                    sizeof(viewtube_background_window_position_t)))
                return -EACCES;
            write_background_position(&vla.background_window_position, VIEWTUBE_BACKGROUND_X_AXIS);
            break;

        case VIEWTUBE_SET_BACKGROUND_POS_Y:
            if (copy_from_user(&vla, (viewtube_arg_t *) arg,
                    sizeof(viewtube_background_window_position_t)))
                return -EACCES;
            write_background_position(&vla.background_window_position, VIEWTUBE_BACKGROUND_Y_AXIS);
            break;

        case VIEWTUBE_GET_BACKGROUND_POS:
            get_background_position_from_device();
            vla.background_window_position.coordinate_x = dev.background_x_pos;
            vla.background_window_position.coordinate_y = dev.background_y_pos;
                if (copy_to_user((viewtube_arg_t *) arg, &vla,
                        sizeof(viewtube_background_window_position_t)))
                    return -EACCES;
            break;

		case VIEWTUBE_UPDATE_SPRITE_AT_INDEX:
			if (copy_from_user(&vla, (viewtube_arg_t *) arg,
                    sizeof(viewtube_sprite_t)))
                return -EACCES;
            write_sprite_to_table(&vla.sprite);
            break;

		case VIEWTUBE_MOVE_SPRITE_TO_NEW_POS:
			if (copy_from_user(&vla, (viewtube_arg_t *) arg,
                    sizeof(viewtube_sprite_t)))
                return -EACCES;
            write_new_sprite_pos(&vla.sprite);
            break;
			break;


        default:
                return -EINVAL;
    }
	return 0;
}

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


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



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

	/* Register ourselves as a misc device: creates /dev/viewtube */
	ret = misc_register(&viewtube_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;
	}
        

	return 0;

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

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

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


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


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

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

module_init(viewtube_init);
module_exit(viewtube_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Allison");
MODULE_DESCRIPTION("Viewtube driver; based on shell by Stephen A. Edwards, Columbia University");
