#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/miscdevice.h>

#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/delay.h>
#include <linux/wrapper.h>	/* for mem_map_reserve */

#include <linux/init.h>
#include <asm/mpc5xxx.h>
#include <bestcomm_api.h>
#include <task_api/bestcomm_cntrl.h>
#include <mgt5200/mgt5200.h>
#include <mgt5200/sdma.h>
#include <asm/mpc5xxx_i2s_ring.h>

#define I2S_RING_MINOR		243

#define I2S_DMABUF_SIZE		4096
#define I2S_DMABUF_NUM		16

/* #define PSC2_CLOCK_EXTERNAL_32 */

static void psc_init (void);
static void psc_enable (void);
static void psc_reset (void);

static struct mpc5xxx_psc *psc = (struct mpc5xxx_psc *) MPC5xxx_PSC2;


static unsigned long i2s_is_open;

static DECLARE_WAIT_QUEUE_HEAD (i2s_wait_rx);
static int newdata = 0;

static char *i2s_ring = NULL;
static unsigned int i2s_ring_head;
static unsigned int i2s_ring_tail;
static int i2s_ring_status;

static TaskId rxtask;
static BDIdx bdi;

static void *i2s_buf_vaddr = NULL;
static dma_addr_t i2s_buf_paddr = 0;

#define i2s_buf_p(i)	((void *)(i2s_buf_paddr + (i) * I2S_DMABUF_SIZE))
#define i2s_buf_v(p)	((void *)((dma_addr_t)i2s_buf_vaddr + ((p) - i2s_buf_paddr)))


static void i2s_rx_save (void *buf)
{
	int rest = i2s_ring_tail - i2s_ring_head;

	if (rest <= 0) {
		rest += I2S_RING_SIZE;
	}

	if (rest > I2S_DMABUF_SIZE) {
		memcpy (i2s_ring + i2s_ring_head, buf, I2S_DMABUF_SIZE);
		i2s_ring_head =
			(i2s_ring_head + I2S_DMABUF_SIZE) % I2S_RING_SIZE;
	} else {
		i2s_ring_status = I2S_RING_STATUS_OVERFLOW;
	}

	newdata = 1;
}

static void i2s_rx_irq (int irq, void *dev_id, struct pt_regs *regs)
{
	TaskBD1_t *bd;

	TaskIntClear (rxtask);

	bd = (TaskBD1_t *) TaskGetBD (rxtask, bdi);

	while (!(bd->Status & SDMA_BD_MASK_READY)) {
		void *p = (void *) bd->DataPtr[0];

		i2s_rx_save (i2s_buf_v (p));

		bdi = TaskBDRelease (rxtask);

		TaskBDAssign (rxtask, p, NULL, I2S_DMABUF_SIZE, 0);

		bd = (TaskBD1_t *) TaskGetBD (rxtask, bdi);
	}

	wake_up (&i2s_wait_rx);
}

static ssize_t i2s_read (struct file *file, char *buf, size_t count,
			 loff_t * ppos)
{
	if (count != 4) {
		return -EINVAL;
	}

	copy_to_user (buf, &i2s_ring_head, 4);

	newdata = 0;

	return 4;
}

static ssize_t i2s_write (struct file *file, const char *buf, size_t count,
			  loff_t * ppos)
{
	unsigned int newtail;

	if (count != 4) {
		return -EINVAL;
	}

	if ((file->f_flags & O_ACCMODE) == O_RDONLY) {
		return -EBADF;
	}

	copy_from_user (&newtail, buf, 4);

	if (newtail >= I2S_RING_SIZE) {
		return -EINVAL;
	}

	if (i2s_ring_tail <= i2s_ring_head &&
	    !(i2s_ring_tail <= newtail && newtail <= i2s_ring_head)) {
		return -EINVAL;
	}

	if (i2s_ring_head < i2s_ring_tail
	    && (i2s_ring_head < newtail && newtail < i2s_ring_tail)) {
		return -EINVAL;
	}

	i2s_ring_status = 0;

	i2s_ring_tail = newtail;

	return 4;
}

static int i2s_open (struct inode *inode, struct file *file)
{
	int i;

	if (test_and_set_bit (0, &i2s_is_open))
		return -EBUSY;

	i2s_is_open = 1;

	i2s_ring_head = 0;
	i2s_ring_tail = 0;
	i2s_ring_status = 0;

	bdi = 0;

	if (request_irq (MPC5xxx_SDMA_IRQ_BASE + rxtask, i2s_rx_irq,
			 SA_INTERRUPT, "i2s rx dma", NULL)) {
		printk (KERN_ERR "i2s: SDMA rx irq allocation failed\n");
		return -EINVAL;
	}

	for (i = 0; i < I2S_DMABUF_NUM; i++) {
		TaskBDAssign (rxtask, i2s_buf_p (i), NULL, I2S_DMABUF_SIZE,
			      0);
	}

	psc_enable ();

	TaskStart (rxtask, TASK_AUTOSTART_ENABLE, rxtask,
		   TASK_INTERRUPT_ENABLE);

	return 0;
}

static int i2s_release (struct inode *inode, struct file *file)
{
	psc_reset ();

	TaskStop (rxtask);
	TaskBDReset (rxtask);
	free_irq (MPC5xxx_SDMA_IRQ_BASE + rxtask, NULL);

	clear_bit (0, &i2s_is_open);
	return 0;
}

static int i2s_mmap (struct file *file, struct vm_area_struct *vma)
{
	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
	unsigned long size = vma->vm_end - vma->vm_start;

	if (offset != 0) {
		printk ("i2s_mmap: invalid offset: must be 0\n");
		return -ENXIO;
	}

	if (size != I2S_RING_SIZE) {
		printk ("i2s_mmap: invalid mmap size: must be %d\n",
			I2S_RING_SIZE);
		return -ENXIO;
	}

	if (remap_page_range (vma->vm_start,
			      virt_to_phys (i2s_ring), size, PAGE_SHARED)) {
		printk ("i2s_mmap: remap_page_range failed\n");
		return -ENXIO;
	}

	return 0;
}

static unsigned int i2s_poll (struct file *file, poll_table * wait)
{
	int ret = 0;

	poll_wait (file, &i2s_wait_rx, wait);

	if (newdata)
		ret = (POLLIN | POLLRDNORM);

	return ret;
}

static int i2s_ioctl (struct inode *inode, struct file *file,
		      unsigned int cmd, unsigned long arg)
{
	switch (cmd) {

	case I2S_RING_GET_TAIL:
		return put_user (i2s_ring_tail, (u32 *) arg);

	case I2S_RING_GET_STATUS:
		return put_user (i2s_ring_status, (u32 *) arg);

	default:
		return -ENOIOCTLCMD;
	}

	return 0;
}

#ifdef PSC2_CLOCK_EXTERNAL_32

static void psc_init (void)
{
	struct mpc5xxx_gpio *gpio = (struct mpc5xxx_gpio *) MPC5xxx_GPIO;

	/* disable Tx & Rx */
	psc->command = (MPC5xxx_PSC_TX_DISABLE | MPC5xxx_PSC_RX_DISABLE);
	/* PSC2 CODEC without Master Clock */
	gpio->port_config &= ~0x70;
	gpio->port_config |= 0x60;

	/* reset PSC */
	psc_reset ();

	psc->mode = 0;
	/* alarm threshold level */
	psc->rfalarm = 0x120;
	/* granularity */
	psc->rfcntl = 4;
	/* disable interrupts */
	psc->mpc5xxx_psc_imr = 0;
	/* Serial Interface Control Register
	 *   Delay Time Slot One
	 *   MSB First
	 *   Codec mode, 32 bit data
	 *   Use bit clock and frame sync provided by external device
	 *   More than one word per frame
	 *   Data sampled on rising edge
	 *   Frame sync is high true
	 */
	psc->sicr = 0x2f700000;
	/* 32 bits per frame */
	psc->ctur = 0x1f;

	/* reset PSC again */
	psc_reset ();
}

#else  /* ! PSC2_CLOCK_EXTERNAL_32 */

static void psc_init (void)
{
	struct mpc5xxx_gpio *gpio = (struct mpc5xxx_gpio *) MPC5xxx_GPIO;
	struct mpc5xxx_cdm *cdm = (struct mpc5xxx_cdm *) MPC5xxx_CDM;

	/* 528MHz/(0x1f+1)=16.5 MHz */
	cdm->mclken_div_psc2 |= (0x8000 | 0x1f);
	/* disable Tx & Rx */
	psc->command = (MPC5xxx_PSC_TX_DISABLE | MPC5xxx_PSC_RX_DISABLE);
	/* PSC2 CODEC with Master Clock */
	gpio->port_config |= 0x70;
	/* PSC2 clock enable */
	cdm->clk_enables |= 0x40;

	/* reset PSC */
	psc_reset ();

	psc->mode = 0;
	/* alarm threshold level */
	psc->rfalarm = 0x120;
	/* granularity */
	psc->rfcntl = 4;
	/* disable interrupts */
	psc->mpc5xxx_psc_imr = 0;
	/* Serial Interface Control Register
	 *   Delay Time Slot One
	 *   MSB First
	 *   Codec mode, 16 bit data
	 *   Use bit clock and frame sync generated internally from Mclk
	 *   More than one word per frame
	 *   Data sampled on rising edge
	 *   Frame sync is low true
	 */
	psc->sicr = 0x22e00000;
	/* 16 bits per frame */
	psc->ctur = 0xf;
	/* 2MHz/(0x3f+1) = 32 kHz - sample rate */
	/* 16.5MHz/(0x7+1) = 2 Mhz */
	psc->ccr = (0x3f << 8) | 0x07;

	/* reset PSC again */
	psc_reset ();
}

#endif /* PSC2_CLOCK_EXTERNAL_32 */

static void psc_enable (void)
{
	psc->command = MPC5xxx_PSC_RX_ENABLE;
}

static void psc_reset (void)
{
	psc->command = MPC5xxx_PSC_RST_RX;
	psc->command = MPC5xxx_PSC_RST_TX;
	psc->command = MPC5xxx_PSC_SEL_MODE_REG_1;
	psc->command = MPC5xxx_PSC_RST_ERR_STAT;
}


static struct file_operations i2s_fops = {
	owner:THIS_MODULE,
	read:i2s_read,
	write:i2s_write,
	open:i2s_open,
	release:i2s_release,
	mmap:i2s_mmap,
	poll:i2s_poll,
	ioctl:i2s_ioctl,
};

static struct miscdevice i2s_miscdev = {
	I2S_RING_MINOR,
	"I2S",
	&i2s_fops
};

static int __init i2s_init (void)
{
	int ret = -1;
	struct page *page;
	TaskSetupParamSet_t i2s_setup_rx;

	if (I2S_RING_SIZE % I2S_DMABUF_SIZE) {
		printk ("i2s: invalid ring buffer size %d - must be multiple of %d\n", I2S_RING_SIZE, I2S_DMABUF_SIZE);
		goto out;
	}

	i2s_ring = kmalloc (I2S_RING_SIZE, GFP_KERNEL);
	if (i2s_ring == NULL) {
		printk (KERN_ERR "i2s: can't alocate ring buffer\n");
		goto out;
	}
	/* remap_page_range() won't allow to map non-reserved pages */
	for (page = virt_to_page (i2s_ring);
	     page < virt_to_page (i2s_ring + I2S_RING_SIZE); page++) {
		mem_map_reserve (page);
	}


	if ((i2s_buf_vaddr = pci_alloc_consistent (NULL,
						   I2S_DMABUF_SIZE *
						   I2S_DMABUF_NUM,
						   &i2s_buf_paddr)) == NULL) {
		printk (KERN_ERR "i2s: can't alocate buffers\n");
		goto out;
	}

	ret = misc_register (&i2s_miscdev);
	if (ret) {
		printk (KERN_ERR "i2s: can't misc_register on minor=%d\n",
			I2S_RING_MINOR);
		goto out;
	}

	psc_init ();

	memset (&i2s_setup_rx, 0, sizeof (i2s_setup_rx));
	i2s_setup_rx.Initiator = (MPC5200Initiator_t) INITIATOR_PSC2_RX;
	i2s_setup_rx.NumBD = I2S_DMABUF_NUM;
	i2s_setup_rx.Size.MaxBuf = I2S_DMABUF_SIZE;
	i2s_setup_rx.StartAddrSrc = (volatile u32) &(psc->rfdata);
	i2s_setup_rx.IncrDst = 4;
	i2s_setup_rx.IncrSrc = 0;
	i2s_setup_rx.SzDst = 4;
	rxtask = TaskSetup (TASK_GEN_RX_BD, &i2s_setup_rx);

	printk (KERN_INFO "MPC5200 I2S driver (%d BDs x %d bytes).\n",
		I2S_DMABUF_NUM, I2S_DMABUF_SIZE);

      out:
	if (ret) {
		if (i2s_buf_vaddr) {
			pci_free_consistent (NULL,
					     I2S_DMABUF_SIZE * I2S_DMABUF_NUM,
					     i2s_buf_vaddr, i2s_buf_paddr);
			i2s_buf_vaddr = NULL;
		}
		if (i2s_ring) {
			kfree (i2s_ring);
			i2s_ring = NULL;
		}
	}

	return ret;

}

static void __exit i2s_exit (void)
{
	pci_free_consistent (NULL, I2S_DMABUF_SIZE * I2S_DMABUF_NUM,
			     i2s_buf_vaddr, i2s_buf_paddr);
	kfree (i2s_ring);

	misc_deregister (&i2s_miscdev);
}

module_init (i2s_init);
module_exit (i2s_exit);

MODULE_DESCRIPTION
	("I2S driver for the MPC5200 PSC2 with ring buffer interface.\n");
MODULE_LICENSE ("GPL");
EXPORT_NO_SYMBOLS;
