/*
 * Copyright (C) 2003-2004 Giuseppe Cavallaro (peppe.cavallaro@st.com)
 *
 * May be copied or modified under the terms of the GNU General Public
 * License.  See linux/COPYING for more information.
 *
 * Interfaces (where required) the co-processors on ST platforms based
 * on multiprocessor architecture, for embedded products like Set-top-Box
 * DVD, etc...
 *
*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/bootmem.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/vmalloc.h>

#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/stm/coprocessor.h>
#include <linux/platform_device.h>
#include <asm/types.h>
#include <asm/uaccess.h>
#include <asm/sections.h>
#include <asm/io.h>
#include <asm/irq.h>

#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int __init proc_st_coproc_init(void);
#endif

/* ---------------------------------------------------------------------------
 *     Local (declared out of order) functions
 * ------------------------------------------------------------------------ */

static int __init parse_coproc_mem(char *from);

/* ---------------------------------------------------------------------------
 * 		Co-processor: Hardware dependent support
 * This includes:
 *    - per platform device and memory addresses
 *    - platform dependent macros
 *    - HW dependent actions required by the generic APIs: Init,
 *      Open, Release, Ioctl (to reset, trigger the start (grant),
 *      peek and poke, etc...) functions
 * ------------------------------------------------------------------------ */

extern struct coproc_board_info coproc_info;

/* ---------------------------------------------------------------------------
 *    Local data structure
 * ------------------------------------------------------------------------ */

extern coproc_t coproc[];	/* The maximum number of copro-  */
					   /* cessors depends on platform   */
					   /* type                          */

#ifdef CONFIG_DEVFS_FS
static devfs_handle_t devfs_reset_hdl;
#endif

#if defined(CONFIG_COPROCESSOR_DEBUG) && defined(RPC_DEBUG)
/* -------------------------------------------------------------------------
 * 			Co-processor DEBUG Suppor26t
 *
 *  Currently we do not expect to receive asynchronous events from the
 *  slave processor. This routine as well as the IRQ definition has been
 *  foreseen and used for debug purposes.
 *  Once a slave is started the standard and supported way of communicating
 *  with the host processor is to rely on the RPC service.
 */

static int irq_count = 0;
static void mbx_irq_handle(int irq, void *cop, struct pt_regs *regs)
{
	/* avoid a noisy loop if possible! */
	irq_count++;
	if ((irq_count % 100) == 0)
		printk("st-coprocessor: unexpected interrupt %d from %s.%u\n",
		       irq, ((coproc_t *) cop)->pdev.name,((coproc_t *) cop)->pdev.id);

	(void)&mbx_irq_handle;	/* warning suppression */
}

#endif				/* CONFIG_COPROCESSOR_DEBUG */

static void __debug(coproc_t * cop, const char *fnc)
{

	/* Print the coprocessor control structure */
	printk("%s.%u Coprocessor -------------------------------------------\n",
	       cop->pdev.name,cop->pdev.id);
	if (cop->control == 0 || cop->ram_size == 0) {
		printk("    not configured!\n");
		goto skip_debug;
	} else {
		printk
		    ("    flags %04x RAM start at 0x%08lx  size      0x%08x\n",
		     cop->control, HOST_ADDR(cop, 0), cop->ram_size);
		printk("                  cop. addr 0x%08lx\n",
		       COPR_ADDR(cop, 0));
	}

#ifdef CONFIG_COPROCESSOR_DEBUG
	if (cop->h2c_port)
		printk
		    ("    Channels : h->c 0x%08x (%08lx)    c->h 0x%08x (%08lx)\n",
					cop->h2c_port, (long unsigned int)readl(cop->h2c_port), cop->c2h_port,
					(long unsigned int)readl(cop->c2h_port));
	else
#endif
		printk("    Channels : Not defined\n");

	if (cop->irq)
		printk("    IRQ      : %d\n", cop->irq);
	else
		printk("    IRQ      : not used\n");

      skip_debug:
	printk
	    ("---------------------------------------------------------------\n");

}

/* ---------------------------------------------------------------------------
 * 			Co-processor driver APIs
 * ------------------------------------------------------------------------ */

static int st_coproc_open(struct inode *inode, struct file *file)
{
	/*
	 ** use minor number (ID) to access the current coproc. descriptor
	 */
	coproc_t *cop = FILE_2_COP(coproc, file);

	DPRINTK(">>> %s: %s-%u, cop->control = %d, cop->ram_size = %d\n",
		__FUNCTION__, cop->pdev.name,cop->pdev.id, cop->control, cop->ram_size);

	if (((cop - coproc) / sizeof(coproc_t)) >= coproc_info.max_coprs)
		return (-ENODEV);
	if (cop->ram_size == 0)
		return (-ENOSPC);
	if (cop->control & COPROC_IN_USE)
		return (-EBUSY);
	cop->control |= COPROC_IN_USE;

	/* Now call the platform dependent open stage */
	coproc_cpu_open(cop);
#ifdef CONFIG_COPROCESSOR_DEBUG
	__debug(cop, __FUNCTION__);
#endif
	return 0;
}

static int st_coproc_release(struct inode *inode, struct file *file)
{
	coproc_t *cop = FILE_2_COP(coproc, file);

	coproc_cpu_release(cop);
	cop->control &= ~COPROC_IN_USE;

	return 0;
}

static int st_coproc_ioctl(struct inode *inode, struct file *file,
			   unsigned int cmd, unsigned long arg)
{
	coproc_t *cop = FILE_2_COP(coproc, file);
	int res = 0;

	switch (cmd) {
	case STCOP_RESET:
		res = coproc_cpu_reset(cop);
		break;
	case STCOP_GRANT:
		/* Release the Slave CPU from reset (do not wait) */
		res = coproc_cpu_grant(cop, arg);
		break;

/* Peek and poke 32 bit cell:  for debug only
 * ------------------------------------------
 * Not generally available, not documented and not supported.
 * Simple and perhaps not reliable way for writing and reading a 32 bit
 * value using the <stslave> application.
 * PAY ATTENTION: the code doesn't make any validity check hence the use
 *                of a wrong address may have undesired effects!
 */
#define PAIRS(p) (p)[0]		/* Number of couple:  addr./value */
#define PORT(p)  (p)[1]		/* the address (port)             */
#define VALUE(p) (p)[2]		/* the 32 bit value               */

	case STCOP_PEEK:
		{
			u_long peek[3];
			res = -EINVAL;

			/* so far we need to peek only a single 32 bit cell */
			res = copy_from_user(peek, (void __user *)arg,
					     sizeof(peek));
			if (res < 0)
				break;
			if (PAIRS(peek) != 1)
				break;

			/* make the addr 32 bit aligned  and peek the value */
			PORT(peek) &= ~0x3;
			VALUE(peek) = peek_l(PORT(peek));

			DPRINTK(">>> %s: %s.%u;  peek[%ld] @0x%08lx = 0x%08lx\n",
				__FUNCTION__, cop->pdev.name,cop->pdev.id,
				PAIRS(peek), PORT(peek), VALUE(peek));

			/* don't mind wich data, make it availbale to the user */
			res = copy_to_user((void *)arg, peek, sizeof(peek));
			break;
		}

	case STCOP_POKE:
		{
			u_long poke[3];
			res = -EINVAL;

			/* so far we need to peek only a single 32 bit cell */
			res = copy_from_user(poke, (void __user *)arg,
					     sizeof(poke));
			if (res < 0)
				break;
			if (PAIRS(poke) != 1)
				break;

			/* make the addr 32 bit aligned  and poke the value */
			PORT(poke) &= ~0x3;
			poke_l(VALUE(poke), PORT(poke));

			DPRINTK(">>> %s: %s.%u;  poke[%ld] @0x%08lx = 0x%08lx\n",
				__FUNCTION__, cop->pdev.name,cop->pdev.id,
				PAIRS(poke), PORT(poke), VALUE(poke));
			res = 0;
			break;
		}

	case STCOP_GET_PROPERTIES:
		{
			cop_properties_t clayout;

//			strncpy(clayout.name, cop->pdev.name,
//				sizeof(clayout.name));
			sprintf(clayout.name,"%s-%u",cop->pdev.name,cop->pdev.id);
			clayout.flags = cop->control;

			clayout.ram_start = HOST_ADDR(cop, 0);
			clayout.ram_size = cop->ram_size;
			clayout.cp_ram_start = COPR_ADDR(cop, 0);

			res =
			    copy_to_user((void *)arg, &clayout,
					 sizeof(cop_properties_t));
			break;
		}

	case STCOP_SET_PROPERTIES:
		{
			/* Not yet supported! */
			printk(KERN_INFO
			       "%s.%u: setting properties not yet available\n",
			       cop->pdev.name,cop->pdev.id);
			res = -ENOSYS;
			break;
		}

	default:
		res = -EINVAL;
	}
	return (res);
}

static ssize_t st_coproc_read(struct file *file, char *buf,
			      size_t count, loff_t * ppos)
{
	coproc_t *cop;
	u_long from;
	ssize_t bytes;
	u_int offset = *ppos;

	cop = FILE_2_COP(coproc, file);

	/*
	 * File position assumes the Coprocessor RAM base addr.
	 * normalaized to 0
	 */
	if (offset >= cop->ram_size)
		return (0);

	from = (u_long) HOST_ADDR(cop, offset);
	bytes = min(count, (cop->ram_size - offset));

	DPRINTK(">>> %s: from 0x%08lx to 0x%08x len 0x%x(%d)\n",
		__FUNCTION__, from, (u_int) buf, bytes, bytes);

	if (copy_to_user(buf, (void *)from, bytes))
		return (-EFAULT);

	*ppos += bytes;

	return (bytes);
}

static ssize_t st_coproc_write(struct file *file, const char *buf,
			       size_t count, loff_t * ppos)
{
	coproc_t *cop;
	u_long to;
	ssize_t bytes;
	u_int offset = (u_int) * ppos;

	cop = FILE_2_COP(coproc, file);

	/* File position assumes the RAM base addr. normalaized to 0 */
	if (offset >= (u_long) cop->ram_size)
		return (-EFBIG);

	to = (u_long) HOST_ADDR(cop, offset);
	bytes = min(count, (cop->ram_size - offset));

	DPRINTK(">>> %s: from 0x%08x to 0x%08lx len 0x%x(%d)\n",
		__FUNCTION__, (u_int) buf, to, bytes, bytes);

	if (copy_from_user((void *)to, buf, bytes))
		return (-EFAULT);

	*ppos += bytes;
	return (bytes);
}

static loff_t st_coproc_llseek(struct file *file, loff_t fpos, int orig)
{
	coproc_t *cop;
	u_long offset = fpos;
	u_long base;

	cop = FILE_2_COP(coproc, file);

	/*
	 * fpos  can be a real offset within the coprocessor region or a
	 * direct host address in the region (coming from application in
	 * case of mmap).  This code assumes to be an address if it abs.
	 * value > COPR_ADDR, a normal offset otherwyse.
	 */
	base = HOST_ADDR(cop, 0);

	DPRINTK
	    (">>> %s: seek to: 0x%08lx ->  fpos = offset 0x%lx + base 0x%lx\n",
	     __FUNCTION__, (u_long) fpos, offset, HOST_ADDR(cop, 0));

	switch (orig) {
	case 0:
		file->f_pos = offset;
		break;		/* SEEK_SET */
	case 1:
		file->f_pos += offset;
		break;		/* SEEK_CUR */
	case 2:
		file->f_pos = cop->ram_size + offset;
		break;		/* SEEK_END */
	default:
		return -EINVAL;
	}

	if (file->f_pos >= cop->ram_size)
		file->f_pos = cop->ram_size - 1;

	return (file->f_pos);

}

static int st_coproc_mmap(struct file *file, struct vm_area_struct *vma)
{
	coproc_t *cop = FILE_2_COP(coproc, file);

	unsigned int offset = vma->vm_pgoff << PAGE_SHIFT;
	unsigned int vsize = vma->vm_end - vma->vm_start;
	unsigned int psize = cop->ram_size - offset;

	DPRINTK(">>> %s: vm_start=0x%lx, vm_end=0x%lx, vm_pgoff=0x%lx\n",
		__FUNCTION__, vma->vm_start, vma->vm_end, vma->vm_pgoff);

	if (vsize > psize)
		return (-ENOSPC);

	/*
	 * Call the remap_pfn_range(...) function to map in user space the
	 * coprocessor memory region. Uses ST40 no cache region
	 */
	vma->vm_flags |= VM_IO;
	/* TODO: double check this. was a call
	 *       to remap_page_range but without the >> PAGE_SHIFT
	 */
	if (remap_pfn_range(vma, vma->vm_start, cop->ram_offset
			    >> PAGE_SHIFT, vsize, vma->vm_page_prot)) {
		DPRINTK(">>> %s: remap_page_range(...) failed\n", __FUNCTION__);
		return (-EAGAIN);
	}

	DPRINTK(">>> %s: ... done: 0x%08lx len 0x%x --> 0x%08lx\n",
		__FUNCTION__, (unsigned long)HOST_ADDR(cop, 0), vsize,
		vma->vm_start);
	return (0);
}

static struct file_operations coproc_fops = {
      llseek:st_coproc_llseek,
      read:st_coproc_read,
      write:st_coproc_write,
      ioctl:st_coproc_ioctl,
      mmap:st_coproc_mmap,
      open:st_coproc_open,
      release:st_coproc_release
};

/* Start: ST-Coprocessor Device Attribute on SysFs*/
static ssize_t st_copro_show_running(struct device *dev,
				     struct device_attribute *attr, char *buf)
{
	coproc_t *cop = container_of(dev, coproc_t, pdev.dev);
	return sprintf(buf, "%d", cop->control & COPROC_IN_USE);
}

static DEVICE_ATTR(running, S_IRUGO, st_copro_show_running, NULL);

static ssize_t st_copro_show_mem_size(struct device *dev,
				      struct device_attribute *attr, char *buf)
{
	coproc_t *cop = container_of(dev, coproc_t, pdev.dev);
	return sprintf(buf, "0x%x", cop->ram_size);
}

static DEVICE_ATTR(mem_size, S_IRUGO, st_copro_show_mem_size, NULL);

static ssize_t st_copro_show_mem_base(struct device *dev,
				      struct device_attribute *attr, char *buf)
{
	coproc_t *cop = container_of(dev, coproc_t, pdev.dev);
	return sprintf(buf, "0x%x", (int)cop->ram_offset);
}

static DEVICE_ATTR(mem_base, S_IRUGO, st_copro_show_mem_base, NULL);
/* End: ST-Coprocessor Device Attribute SysFs*/

static int st_coproc_driver_probe(struct platform_device *dev)
{
	if (!strncmp("st2", dev->name, 3))
		return 1;
	return 0;
}

#if defined(CONFIG_PM)
static int st_coproc_suspend(struct platform_device * dev, pm_message_t state)
{
	printk("st_coproc_suspend: %s.%u down\n",dev->name,dev->id);
	return 0;
}
static int st_coproc_resume(struct platform_device * dev)
{
	printk("st_coproc_resume: %s.%u up\n",dev->name,dev->id);
	return 0;
}
#endif

static struct platform_driver st_coproc_driver = {
	.driver.name = "st-copro",
	.driver.owner = THIS_MODULE,
	.probe = st_coproc_driver_probe,
#if defined(CONFIG_PM)
	.suspend =st_coproc_suspend,
	.resume = st_coproc_resume,
#endif
};

static struct class *coproc_dev_class;
static int __init st_coproc_init(void)
{
	int i;
	coproc_t *cop;
	struct platform_device *pdev;

	printk("STMicroelectronics - Coprocessors %s Init\n", coproc_info.name);

	if (platform_driver_register(&st_coproc_driver)){
		printk(KERN_ERR
		       "Error on ST-Coprocessor device driver registration\n");
		return (-EAGAIN);
	}

	if (register_chrdev(COPROCESSOR_MAJOR, coproc_info.name, &coproc_fops)) {
		printk("Can't allocate major %d for ST Coprocessor Devices\n",
		       COPROCESSOR_MAJOR);
		return (-EAGAIN);
	}

	coproc_dev_class = class_create(THIS_MODULE, "coproc-dev");

	for (cop = &coproc[0], i = 0; i < coproc_info.max_coprs; i++, cop++) {
		if (!cop->ram_offset) {
			printk("st-coprocessor-%d: No RAM reserved\n", i);
			cop->control &= ~COPROC_SPACE_ALLOCATE;
		} else {
			cop->control |= COPROC_SPACE_ALLOCATE;
			cop->vma_address =
				(int)ioremap_nocache((unsigned long)cop->ram_offset, cop->ram_size);
		}
		/*
		 ** Nodes:
		 **    STb7100         : /dev/st231-0    c   63   0
		 **                    : /dev/st231-1    c   63   1
		 ** if the device file system support is configured the above
		 ** devices are autonatically generated
		 */
		pdev = &(cop->pdev);
		memset(pdev, 0, sizeof(struct platform_device));
		pdev->name = coproc_info.name;
		pdev->id   = i;
		pdev->dev.driver = &st_coproc_driver.driver;
		/* Now complete with the platform dependent init stage */
		if (coproc_cpu_init(cop)){
			printk(KERN_ERR
				"CPU %d : HW dep. initialization failed!\n", i);
			continue;
		}
		if (platform_device_register(pdev)<0)
			printk(KERN_ERR
			       "Error on ST-Coprocessor device registration\n");
		else {
			/* Add the attributes on the device */
			if(device_create_file(&pdev->dev, &dev_attr_mem_base) |
					device_create_file(&pdev->dev, &dev_attr_mem_size) |
					device_create_file(&pdev->dev, &dev_attr_running))
				printk(KERN_ERR "Error to add attribute to the coprocessor device\n");
			/* Create the device file via Discovery System */
			cop->dev = device_create(coproc_dev_class, NULL,
						MKDEV(COPROCESSOR_MAJOR,pdev->id),
						NULL,"st231-%d", pdev->id);
		}

		/* Now complete with the platform dependent init stage */
#if defined(CONFIG_COPROCESSOR_DEBUG) && defined(RPC_DEBUG)
		{
			/* just to debug on STi5528 application using the RPC 2.1,
			 * install a fake interrupt hadler
			 */
			int res;

			cop->irq = EMBX_IRQ + i;
			if ((res = request_irq(cop->irq, &mbx_irq_handle, 0,
					       coproc_info.name, cop)) != 0) {
				printk
				    ("st-coprocessor: Error %d booking IRQ %d for %s\n",
				     res, cop->irq, cop->name);
				cop->irq = 0;
			}
		}
#endif
		__debug(cop, __FUNCTION__);
	}

#ifdef CONFIG_PROC_FS
	proc_st_coproc_init();
#endif

	return (0);
}

static void __exit st_coproc_exit(void)
{
	DPRINTK("Release coprocessor module...\n");
}

/*
 * Parse the optional kernel argument:
 *
 * ... coprocessor_mem=size_0@phis_address_0, size_1@phis_address_1
 *
 * It seems to be reasonable to assume that in a "staically partitioned
 * RAM layout", the regions of RAM assigned to each slave processor are
 * not scattered in memory!
 */
static int __init parse_coproc_mem(char *from)
{
	char *cmdl = (from);	/* start scan from '=' char */
	u_long size, addr;
	int i = 0;
	char *error_msg;
	static char size_error[] __initdata =
		KERN_ERR "st-coprocessor: Error parsing size\n";
	static char addr_error[] __initdata =
		KERN_ERR "st-coprocessor: Error parsing address\n";
	static char too_many_warn[] __initdata =
		KERN_WARNING "st-coprocessor: More regions than coprocessors\n";
	static char alloc_error[] __initdata =
		KERN_ERR "st-coprocessor: Failed to reserve memory at 0x%08x\n";

	while (*cmdl && (i < coproc_info.max_coprs)) {
		size = memparse(cmdl, &cmdl);
		if (*cmdl != '@') {
			error_msg = size_error;
			goto args_error;
		}
		addr = memparse(cmdl+1, &cmdl);
		if (*cmdl) {
			if (*cmdl++ != ',') {
				error_msg = addr_error;
				goto args_error;
			}
		}
		coproc[i].ram_offset = addr;
		coproc[i].ram_size = size;
		++i;
	}

	if (*cmdl) {
		printk(too_many_warn);
	}

	for (i = 0; i < coproc_info.max_coprs; ++i) {
		if (coproc[i].ram_size) {
			void* mem;
			addr = coproc[i].ram_offset;
			size = coproc[i].ram_size;
			mem = __alloc_bootmem_nopanic(size, PAGE_SIZE, addr);
			if (mem != __va(addr)) {
				if (mem) {
					free_bootmem(virt_to_phys(mem), size);
				}
				/* At this point, if addr overlaps kernel
				 * memory, coprocessor won't be allocated.
				 */
				if (coproc_check_area(addr, size, i, coproc))
					printk(alloc_error, addr);
			}
		}
	}

	return 1;

args_error:
	printk(error_msg);
	return 1;
}

__setup("coprocessor_mem=", parse_coproc_mem);

MODULE_DESCRIPTION("Co-processor manager for multi-core devices");
MODULE_AUTHOR("STMicroelectronics Limited");
MODULE_VERSION("0.3");
MODULE_LICENSE("GPL");

module_init(st_coproc_init);
module_exit(st_coproc_exit);

#ifdef CONFIG_PROC_FS

static int show_st_coproc(struct seq_file *m, void *v)
{
	int i;
	coproc_t *cop;
	seq_printf(m, "Coprocessors: %d  %s\n",
		   coproc_info.max_coprs, coproc_info.name);
	seq_printf(m,
		   "  CPU (dev)        Host addr.     Copr. addr.     Size\n");
	seq_printf(m,
		   "  -------------------------------------------------------------------\n");
	for (i = 0, cop = &coproc[0]; i < coproc_info.max_coprs; i++, cop++) {
		seq_printf(m, "  /dev/%s-%u    ", cop->pdev.name,cop->pdev.id);
		if (cop->ram_size == 0)
			seq_printf(m, "not allocated!\n");
		else
			seq_printf(m,
				   "0x%08lx     0x%08lx      0x%08x (%2d Mb)\n",
				   HOST_ADDR(cop, 0), COPR_ADDR(cop, 0),
				   cop->ram_size, (cop->ram_size / MEGA));
	}
	seq_printf(m, "\n");

	coproc_proc_other_info(cop, m);
	return (0);
}

static void *st_coproc_seq_start(struct seq_file *m, loff_t * pos)
{
	return (void *)(*pos == 0);
}

static void *st_coproc_seq_next(struct seq_file *m, void *v, loff_t * pos)
{
	return NULL;
}

static void st_coproc_seq_stop(struct seq_file *m, void *v)
{
}

static struct seq_operations proc_st_coproc_op = {
      start:st_coproc_seq_start,
      next:st_coproc_seq_next,
      stop:st_coproc_seq_stop,
      show:show_st_coproc,
};

static int proc_st_coproc_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &proc_st_coproc_op);
}

static struct file_operations proc_st_coproc_operations = {
      open:proc_st_coproc_open,
      read:seq_read,
      llseek:seq_lseek,
      release:seq_release,
};

static int __init proc_st_coproc_init(void)
{
	struct proc_dir_entry *entry;
	entry = create_proc_entry("coprocessor", 0, NULL);
	if (entry != NULL) {
		entry->proc_fops = &proc_st_coproc_operations;
	}

	return 0;
}

#endif				/* CONFIG_PROC_FS */