/* * Device driver for the VGA video generator
 *
 * A Platform device implemented using the misc subsystem
 *
 * 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 vga_ball.ko
 *
 * Check code style with
 * checkpatch.pl --file --no-tree vga_ball.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 "vga_ball.h"

#define DRIVER_NAME "vga_ball"

/* Device registers */

#define G_LOC_X(x) (x)
#define G_LOC_Y(x) ((x) + 2)
#define TANK_DATA(x) ((x) + 4)

#define B_LOC_X(x) ((x) + 6)
#define B_LOC_Y(x) ((x) + 8)
#define AUDIO(x) ((x) + 10)

#define GB_LOC_X(x) ((x) + 12)
#define GB_LOC_Y(x) ((x) + 14)
#define GB_DATA(x) ((x) + 16)

#define BB_LOC_X(x) ((x) + 18)
#define BB_LOC_Y(x) ((x) + 20)
#define BB_DATA(x) ((x) + 22)

#define EXP_LOC_X(x) ((x) + 24)
#define EXP_LOC_Y(x) ((x) + 26)
#define EXP_IDX(x) ((x) + 28)

#define SCORE(x) ((x) + 30)
#define CONTROLLER(x) ((x) + 34)

/*
 * Information about our device
 */
struct tank_dev
{
	struct resource res;	/* Resource: our registers */
	void __iomem *virtbase; /* Where registers can be accessed in memory */
	tank_loc_t location;
	tank_data_t data;
	object_data_t explosion_data;
	score_data_t score_data;
	unsigned short sound_data;
} dev;

/*
 * Write segments of a single digit
 * Assumes digit is in range and the device information has been set up
 */

// GREEN TANK
static void write_gtank_loc(tank_loc_t *location)
{
	iowrite16(location->x, G_LOC_X(dev.virtbase));
	iowrite16(location->y, G_LOC_Y(dev.virtbase));
	dev.location = *location;
}
static void write_tank_info(tank_data_t *data)
{
	iowrite16(data->gtank_dis_dir_btank_dis_dir, TANK_DATA(dev.virtbase));
	dev.data = *data;
}

// BLUE TANK
static void write_btank_loc(tank_loc_t *location)
{
	iowrite16(location->x, B_LOC_X(dev.virtbase));
	iowrite16(location->y, B_LOC_Y(dev.virtbase));
	dev.location = *location;
}
static void select_sound(unsigned short *data)
{
	printk("here data\n");
	iowrite16((unsigned int)(*data), AUDIO(dev.virtbase));
	dev.sound_data = *data;
	printk("done\n");
}

// GREEN BULLET
static void write_gbullet_loc(tank_loc_t *location)
{
	iowrite16(location->x, GB_LOC_X(dev.virtbase));
	iowrite16(location->y, GB_LOC_Y(dev.virtbase));
	dev.location = *location;
}
static void write_gbullet_info(tank_data_t *data)
{
	iowrite16(data->gtank_dis_dir_btank_dis_dir, GB_DATA(dev.virtbase));
	dev.data = *data;
}

// BLUE BULLET
static void write_bbullet_loc(tank_loc_t *location)
{
	iowrite16(location->x, BB_LOC_X(dev.virtbase));
	iowrite16(location->y, BB_LOC_Y(dev.virtbase));
	dev.location = *location;
}
static void write_bbullet_info(tank_data_t *data)
{
	iowrite16(data->gtank_dis_dir_btank_dis_dir, BB_DATA(dev.virtbase));
	dev.data = *data;
}

// EXPLOSION
static void write_exp_data(object_data_t *data)
{
	iowrite16(data->location.x, EXP_LOC_X(dev.virtbase));
	iowrite16(data->location.y, EXP_LOC_Y(dev.virtbase));
	iowrite16(data->index, EXP_IDX(dev.virtbase));
	dev.explosion_data = *data;
}

static void write_score_data(score_data_t *data)
{
	iowrite16(data->score, SCORE(dev.virtbase));
	dev.score_data = *data;
}
/*
 * Handle ioctl() calls from userspace:
 * Read or write the segments on single digits.
 * Note extensive error checking of arguments
 */
static long vga_ball_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
	tank_arg_t vla;

	switch (cmd)
	{
	case GTANK_WRITE_LOC:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_gtank_loc(&vla.location);
		break;

	case GTANK_READ_LOC:
		vla.location = dev.location;
		if (copy_to_user((tank_arg_t *)arg, &vla,
						 sizeof(tank_arg_t)))
			return -EACCES;
		break;

	case TANK_DATA_WRITE:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_tank_info(&vla.data);
		break;

	case BTANK_WRITE_LOC:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_btank_loc(&vla.location);
		break;

	case BTANK_READ_LOC:
		vla.location = dev.location;
		if (copy_to_user((tank_arg_t *)arg, &vla,
						 sizeof(tank_arg_t)))
			return -EACCES;
		break;

	case GBULLET_WRITE_LOC:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_gbullet_loc(&vla.location);
		break;

	case GBULLET_READ_LOC:
		vla.location = dev.location;
		if (copy_to_user((tank_arg_t *)arg, &vla,
						 sizeof(tank_arg_t)))
			return -EACCES;
		break;
	case GBULLET_DATA:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_gbullet_info(&vla.data);
		break;
	case BBULLET_WRITE_LOC:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_bbullet_loc(&vla.location);
		break;

	case BBULLET_READ_LOC:
		vla.location = dev.location;
		if (copy_to_user((tank_arg_t *)arg, &vla,
						 sizeof(tank_arg_t)))
			return -EACCES;
		break;
	case BBULLET_DATA:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_bbullet_info(&vla.data);
		break;

	case EXP_WRITE_DATA:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_exp_data(&vla.explosion_data);
		break;
	case SCORE_WRITE_DATA:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		write_score_data(&vla.score_data);
		break;
	case AUDIO_SELECT:
		if (copy_from_user(&vla, (tank_arg_t *)arg,
						   sizeof(tank_arg_t)))
			return -EACCES;
		printk("debug %d\n", vla.sound_selection);
		select_sound(&vla.sound_selection);
		break;
	case test_read_gtank:
		vla.location.x = ioread16(G_LOC_X(dev.virtbase));
		vla.location.y = ioread16(G_LOC_Y(dev.virtbase));
		if (copy_to_user((tank_arg_t *)arg, &vla,
						 sizeof(tank_arg_t)))
			return -EACCES;
		break;

	case READ_CONTROLS:
		vla.controller_input = ioread16(SCORE(dev.virtbase));
		printk("ioread %d\n", vla.controller_input);
		if (copy_to_user((tank_arg_t *)arg, &vla,
						 sizeof(tank_arg_t)))
		{
			printk(KERN_ERR "copytouser failed\n");
			return -EACCES;
		}
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

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

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

/*
 * Initialization code: get resources (registers) and display
 * a welcome message
 */
static int __init vga_ball_probe(struct platform_device *pdev)
{
	tank_loc_t start = {50, 50};
	tank_loc_t start2 = {100, 100};
	tank_loc_t start3 = {150, 150};
	tank_loc_t start4 = {250, 250};
	int ret;

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

	/* Set an initial location */

	write_gtank_loc(&start);
	write_btank_loc(&start2);
	write_gbullet_loc(&start3);
	write_bbullet_loc(&start4);

	return 0;

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

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

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

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

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

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

module_init(vga_ball_init);
module_exit(vga_ball_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stephen A. Edwards, Columbia University");
MODULE_DESCRIPTION("VGA ball driver");
