#include <linux/kernel.h> 
#include <linux/module.h>
#include <asm/uaccess.h> 		/*Needed for copy_from_user */
#include <asm/io.h>	 		/*Needed for IO Read/Write Functions */
#include <linux/proc_fs.h>		/*Needed for Proc File System Functions */
#include <linux/seq_file.h>		/*Needed for Sequence File Operations */
#include <linux/platform_device.h>	/*Needed for Platform Driver Functions */

/* Define Driver Name */
#define DRIVER_NAME "myiir"

unsigned long *base_addr;	/* Vitual Base Address */
struct resource *res;		/* Device Resource Structure */
unsigned long remap_size;	/* Device Memory Size */

/* Write operation for /proc/myiir
* -----------------------------------
* When user cat a string to /proc/myiir file, the string will be stored in
* const char __user *buf. This function will copy the string from user
* space into kernel space, and change it to an unsigned long value.
* It will then write the value to the register of myiir controller,
* and turn on the corresponding LEDs eventually.
*/

static ssize_t proc_myiir_write(struct file *file, const char __user * buf,
	size_t count, loff_t * ppos)
{
	char myiir_phrase[16];
	u32 myiir_value;

	if (count < 11) {
		if (copy_from_user(myiir_phrase, buf, count))
			return -EFAULT;

		myiir_phrase[count] = '\0';
	}

	myiir_value = simple_strtoul(myiir_phrase, NULL, 0);
	wmb();
	iowrite32(myiir_value, base_addr);
	return count;
}

/* Callback function when opening file /proc/myiir
* ------------------------------------------------------
* Read the register value of myiir controller, print the value to
* the sequence file struct seq_file *p. In file open operation for /proc/myiir
* this callback function will be called first to fill up the seq_file,
* and seq_read function will print whatever in seq_file to the terminal.
*/
static int proc_myiir_show(struct seq_file *p, void *v)
{
	u32 myiir_value;
	myiir_value = ioread32(base_addr);
	seq_printf(p, "0x%x", myiir_value);
	return 0;
}

/* Open function for /proc/myiir
* ------------------------------------
* When user want to read /proc/myiir (i.e. cat /proc/myiir), the open function
* will be called first. In the open function, a seq_file will be prepared and the
* status of myiir will be filled into the seq_file by proc_myiir_show function.
*/

static int proc_myiir_open(struct inode *inode, struct file *file)
{
	unsigned int size = 16;
	char *buf;
	struct seq_file *m;
	int res;

	buf = (char *)kmalloc(size * sizeof(char), GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	res = single_open(file, proc_myiir_show, NULL);

	if (!res) {
		m = file->private_data;
		m->buf = buf;
		m->size = size;
	} else {
		kfree(buf);
	}

	return res;
}

/* File Operations for /proc/myiir */
static const struct file_operations proc_myiir_ch0_operations = {
	.open = proc_myiir_open,
	.read = seq_read,
	.write = proc_myiir_write,
	.llseek = seq_lseek,
	.release = single_release
};

static const struct file_operations proc_myiir_ch1_operations = {
	.read = seq_read,
	.write = proc_myiir_write,
};

static const struct file_operations proc_myiir_mix0_operations = {
	.read = seq_read,
	.write = proc_myiir_write,
};
/* Shutdown function for myiir
* -----------------------------------
* Before myiir shutdown, turn-off all the leds
*/
static void myiir_shutdown(struct platform_device *pdev)
{
	iowrite32(0, base_addr);
}

/* Remove function for myiir
* ----------------------------------
* When myiir module is removed, turn off all the leds first,
* release virtual address and the memory region requested.
*/
static int myiir_remove(struct platform_device *pdev)
{
	myiir_shutdown(pdev);

	/* Remove /proc/myiir entry */
	remove_proc_entry("myiir-audio-ch0", NULL);
	remove_proc_entry("myiir-audio-ch1", NULL);
	remove_proc_entry("myiir-audio-mix0", NULL);

	/* Release mapped virtual address */
	iounmap(base_addr);

	/* Release the region */
	release_mem_region(res->start, remap_size);

	return 0;
}

/* Device Probe function for myiir
* ------------------------------------
* Get the resource structure from the information in device tree.
* request the memory region needed for the controller, and map it into
* kernel virtual memory space. Create an entry under /proc file system
* and register file operations for that entry.
*/
static int __devinit myiir_probe(struct platform_device *pdev)
{
	struct proc_dir_entry *myiir_proc_entry[3];
	int ret = 0;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "No memory resource\n");
		return -ENODEV;
	}
	remap_size = res->end - res->start + 1;

	if (!request_mem_region(res->start, remap_size, pdev->name)) {
		dev_err(&pdev->dev, "Cannot request IO\n");
		return -ENXIO;
	}

	base_addr = ioremap(res->start, remap_size);
	if (base_addr == NULL) {
		dev_err(&pdev->dev, "Couldn't ioremap memory at 0x%08lx\n",
		(unsigned long)res->start);
		ret = -ENOMEM;
		goto err_release_region;
	}

  	/*
	 * XXX: Seperate device nodes for channels and mixer
	 */
	myiir_proc_entry[0] = proc_create("myiir-audio-ch0", 0, NULL,
		&proc_myiir_ch0_operations);

	if (myiir_proc_entry[0] == NULL) {
		dev_err(&pdev->dev, "Couldn't create proc entry\n");
		ret = -ENOMEM;
		goto err_create_proc_entry;
	}

	myiir_proc_entry[1] = proc_create("myiir-audio-ch1", 0, NULL,
		&proc_myiir_ch1_operations);

	if (myiir_proc_entry[1] == NULL) {
		dev_err(&pdev->dev, "Couldn't create proc entry\n");
		ret = -ENOMEM;
		goto err_create_proc_entry;
	}

	myiir_proc_entry[2] = proc_create("myiir-audio-mix0", 0, NULL,
		&proc_myiir_mix0_operations);

	if (myiir_proc_entry[2] == NULL) {
		dev_err(&pdev->dev, "Couldn't create proc entry\n");
		ret = -ENOMEM;
		goto err_create_proc_entry;
	}

	printk(KERN_INFO DRIVER_NAME " probed at VA 0x%08lx\n",
		(unsigned long) base_addr);

	return 0;

	err_create_proc_entry:
		iounmap(base_addr);
	err_release_region:
		release_mem_region(res->start, remap_size);

	return ret;
}

/* device match table to match with device node in device tree
 * These are the list of devices that we want to expose as platform device
 */
static const struct of_device_id myiir_of_match[] __devinitconst = {
	{.compatible = "dglnt,myiir-audio-ch0"},
	{.compatible = "dglnt,myiir-audio-ch1"},
	{.compatible = "dglnt,myiir-audio-mix0"},
	{},
};

MODULE_DEVICE_TABLE(of, myiir_of_match);

/* platform driver structure for myiir driver */
static struct platform_driver myiir_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = myiir_of_match},
	.probe = myiir_probe,
	.remove = __devexit_p(myiir_remove),
	.shutdown = __devexit_p(myiir_shutdown)
};

/* Register myiir platform driver */
module_platform_driver(myiir_driver);

/* Module Infomations */
MODULE_LICENSE("GPL");
MODULE_ALIAS(DRIVER_NAME);
