/* *****************************************************************
 * (C) Copyright 2007 Benjamin Krill <ben@codiert.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 * MA 02110-1301 USA
 * *****************************************************************/
#include <linux/pagemap.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include "fpgafs.h"

#define FPGAFS_MAX_LLDRV 3

static struct fpgafs_lldrv *lldrv[FPGAFS_MAX_LLDRV];
static struct fpgafs_lldrv *lldrv_cur;
static int lldrv_count = 0x0;
static DEFINE_SPINLOCK(fpgafs_lldrv_lock);

struct fpga_context* alloc_fpga_context(void)
{
	struct fpga_context *ctx;
	ctx = kzalloc(sizeof *ctx, GFP_KERNEL);
	if (!ctx)
		goto out;

	/* initialize the struct*/
	ctx->lldrv = -1;

out:
	return ctx;
}

void free_fpga_context(struct fpga_context *ctx)
{
	if (ctx->load_buf)
		kfree(ctx->load_buf);

	kfree(ctx);
	return;
}

ssize_t fpgafs_send_data(struct file *file, const char __user *buf,
                                size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	return (fcur->lldrv > -1) ?
		lldrv[fcur->lldrv]->send(fcur, buf, len)
		: -EBUSY;
}
//EXPORT_SYMBOL_GPL(fpgafs_send_data);

ssize_t fpgafs_recv_data(struct file *file, char __user *buf,
                                size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	return (fcur->lldrv > -1) ?
		lldrv[fcur->lldrv]->recv(fcur, buf, len)
		: -EBUSY;
}
//EXPORT_SYMBOL_GPL(fpgafs_recv_data);

ssize_t fpgafs_write_load(struct file *file, const char __user *buf,
                                size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	return (fcur->lldrv > -1) ?
		lldrv[fcur->lldrv]->write_load(fcur, buf, len)
		: -EBUSY;
}
//EXPORT_SYMBOL_GPL(fpgafs_write_load);

ssize_t fpgafs_read_load(struct file *file, char __user *buf,
                                size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	return (fcur->lldrv > -1) ?
		lldrv[fcur->lldrv]->read_load(fcur, buf, len)
		: -EBUSY;
}
//EXPORT_SYMBOL_GPL(fpgafs_read_load);

/* set/get current low level driver */
ssize_t fpgafs_read_lldrv(struct file *file, char __user *buf,
                                size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	size_t l = (len > 5)?5:len;
	if (fcur->lldrv > -1) {
		if (copy_to_user(buf, lldrv[fcur->lldrv]->name, l))
			return -EFAULT;
	} else {
		return -EBUSY;
	}
	return l;
}
//EXPORT_SYMBOL_GPL(fpgafs_read_lldrv);

ssize_t fpgafs_write_lldrv(struct file *file, const char __user *buf,
                                size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	u32 cp = 0, i;
	u8 __user *usr;
	unsigned char tmp[5];
	size_t l = (len > 5)?5:len;

	/* get name */
	while (cp < l) {
		usr = (u8*)&buf[cp];
		if (__get_user(tmp[cp], usr))
			return -EFAULT;
		cp++;
	}

	for(i=0; i < FPGAFS_MAX_LLDRV; i++)
		if (lldrv[i] != NULL) {
			for (cp = 0; cp < l; cp++) {
				if (lldrv[i]->name[cp] != tmp[cp])
					break;
			}
			if (cp == l) {
				fcur->lldrv = i;
				return l;
			}
		}

	fcur->lldrv = -1;
	return -1;
}
//EXPORT_SYMBOL_GPL(fpgafs_write_lldrv);

ssize_t fpgafs_read_stat(struct file *file, char __user *buf,
                         size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	return (fcur->lldrv > -1) ?
		lldrv[fcur->lldrv]->stat(fcur, buf, len)
		: -EBUSY;
}

ssize_t fpgafs_write_cmd(struct file *file, const char __user *buf,
                         size_t len, loff_t *pos)
{
	struct fpga_context *fcur = (struct fpga_context*)file->private_data;
	return (fcur->lldrv > -1) ?
		lldrv[fcur->lldrv]->cmd(fcur, buf, len)
		: -EBUSY;
}

/* low level un-/register functions */
int fpgafs_register_lldrv(struct fpgafs_lldrv *drv)
{
	unsigned long flags;
	int i;

	spin_lock_irqsave(&fpgafs_lldrv_lock, flags);
	if (lldrv_count == FPGAFS_MAX_LLDRV)
		return -EBUSY;

	/* find free space */
	for(i=0; i < FPGAFS_MAX_LLDRV; i++)
		if (lldrv[i] == NULL) {
			lldrv[i] = drv;
			break;
		}

	if (lldrv[i]->init)
		lldrv[i]->init();

	lldrv_cur = lldrv[i];

	lldrv_count++;

	spin_unlock_irqrestore(&fpgafs_lldrv_lock, flags);
	return 0;
}
EXPORT_SYMBOL_GPL(fpgafs_register_lldrv);

int fpgafs_unregister_lldrv(struct fpgafs_lldrv *drv)
{
	unsigned long flags;
	int i,k;

	spin_lock_irqsave(&fpgafs_lldrv_lock, flags);

	for(i=0; i < FPGAFS_MAX_LLDRV; i++) {
		if (lldrv[i] == drv) {

			/* call the exit function */
			if (lldrv[i]->exit)
				lldrv[i]->exit();

			lldrv[i] = NULL;

			/* if current, search another low level driver */
			if (lldrv_cur == drv) {
				lldrv_cur = NULL;
				for(k=0; k < FPGAFS_MAX_LLDRV; k++)
					if (lldrv[k]) {
						lldrv_cur = lldrv[k];
						break;
					}
			}
			break;
		}
	}

	lldrv_count--;

	spin_unlock_irqrestore(&fpgafs_lldrv_lock, flags);
	return 0;
}
EXPORT_SYMBOL_GPL(fpgafs_unregister_lldrv);
