/*
 * linux/drivers/mxc/mlb/mxc_mlb.c
 *
 * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/cdev.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/mxc_mlb.h>
#include <linux/uaccess.h>
#include <linux/iram_alloc.h>

#include <mach/hardware.h>

/*!
 * MLB module memory map registers define
 */
#define MLB_REG_DCCR	0x0
#define MLB_REG_SSCR	0x4
#define MLB_REG_SDCR	0x8
#define MLB_REG_SMCR	0xC
#define MLB_REG_VCCR	0x1C
#define MLB_REG_SBCR	0x20
#define MLB_REG_ABCR	0x24
#define MLB_REG_CBCR	0x28
#define MLB_REG_IBCR	0x2C
#define MLB_REG_CICR	0x30
#define MLB_REG_CECRn	0x40
#define MLB_REG_CSCRn	0x44
#define MLB_REG_CCBCRn	0x48
#define MLB_REG_CNBCRn	0x4C
#define MLB_REG_LCBCRn	0x280

#define MLB_DCCR_FS_OFFSET	28
#define MLB_DCCR_EN		(1 << 31)
#define MLB_DCCR_LBM_OFFSET	30
#define MLB_DCCR_RESET		(1 << 23)
#define MLB_CECR_CE		(1 << 31)
#define MLB_CECR_TR		(1 << 30)
#define MLB_CECR_CT_OFFSET	28
#define MLB_CECR_MBS		(1 << 19)
#define MLB_CSCR_CBPE		(1 << 0)
#define MLB_CSCR_CBDB		(1 << 1)
#define MLB_CSCR_CBD		(1 << 2)
#define MLB_CSCR_CBS		(1 << 3)
#define MLB_CSCR_BE		(1 << 4)
#define MLB_CSCR_ABE		(1 << 5)
#define MLB_CSCR_LFS		(1 << 6)
#define MLB_CSCR_PBPE		(1 << 8)
#define MLB_CSCR_PBDB		(1 << 9)
#define MLB_CSCR_PBD		(1 << 10)
#define MLB_CSCR_PBS		(1 << 11)
#define MLB_CSCR_RDY		(1 << 16)
#define MLB_CSCR_BM		(1 << 31)
#define MLB_CSCR_BF		(1 << 30)
#define MLB_SSCR_SDML		(1 << 5)

#define MLB_CONTROL_TX_CHANN		(0 << 4)
#define MLB_CONTROL_RX_CHANN		(1 << 4)
#define MLB_ASYNC_TX_CHANN		(2 << 4)
#define MLB_ASYNC_RX_CHANN		(3 << 4)

#define MLB_MINOR_DEVICES		2
#define MLB_CONTROL_DEV_NAME		"ctrl"
#define MLB_ASYNC_DEV_NAME		"async"

#define TX_CHANNEL			0
#define RX_CHANNEL			1
#define TX_CHANNEL_BUF_SIZE		1024
#define RX_CHANNEL_BUF_SIZE		2*1024
/* max package data size */
#define ASYNC_PACKET_SIZE			1024
#define CTRL_PACKET_SIZE			64
#define RX_RING_NODES			10

#define MLB_IRAM_SIZE			(MLB_MINOR_DEVICES * (TX_CHANNEL_BUF_SIZE + RX_CHANNEL_BUF_SIZE))
#define _get_txchan(dev)		mlb_devinfo[dev].channels[TX_CHANNEL]
#define _get_rxchan(dev)		mlb_devinfo[dev].channels[RX_CHANNEL]

enum {
	MLB_CTYPE_SYNC,
	MLB_CTYPE_ISOC,
	MLB_CTYPE_ASYNC,
	MLB_CTYPE_CTRL,
};

/*!
 * Rx ring buffer
 */
struct mlb_rx_ringnode {
	int size;
	char *data;
};

struct mlb_channel_info {

	/* channel offset in memmap */
	const unsigned int reg_offset;
	/* channel address */
	int address;
	/*!
	 * channel buffer start address
	 * for Rx, buf_head pointer to a loop ring buffer
	 */
	unsigned long buf_head;
	/* physical buffer head address */
	unsigned long phy_head;
	/* channel buffer size */
	unsigned int buf_size;
	/* channel buffer current ptr */
	unsigned long buf_ptr;
	/* buffer spin lock */
	rwlock_t buf_lock;
};

struct mlb_dev_info {

	/* device node name */
	const char dev_name[20];
	/* channel type */
	const unsigned int channel_type;
	/* channel info for tx/rx */
	struct mlb_channel_info channels[2];
	/* rx ring buffer */
	struct mlb_rx_ringnode rx_bufs[RX_RING_NODES];
	/* rx ring buffer read/write ptr */
	unsigned int rdpos, wtpos;
	/* exception event */
	unsigned long ex_event;
	/* channel started up or not */
	atomic_t on;
	/* device open count */
	atomic_t opencnt;
	/* wait queue head for channel */
	wait_queue_head_t rd_wq;
	wait_queue_head_t wt_wq;
	/* spinlock for event access */
	spinlock_t event_lock;
};

static struct mlb_dev_info mlb_devinfo[MLB_MINOR_DEVICES] = {
	{
	 .dev_name = MLB_CONTROL_DEV_NAME,
	 .channel_type = MLB_CTYPE_CTRL,
	 .channels = {
		      [0] = {
			     .reg_offset = MLB_CONTROL_TX_CHANN,
			     .buf_size = TX_CHANNEL_BUF_SIZE,
			     .buf_lock =
			     __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[0].
						buf_lock),
			     },
		      [1] = {
			     .reg_offset = MLB_CONTROL_RX_CHANN,
			     .buf_size = RX_CHANNEL_BUF_SIZE,
			     .buf_lock =
			     __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[1].
						buf_lock),
			     },
		      },
	 .on = ATOMIC_INIT(0),
	 .opencnt = ATOMIC_INIT(0),
	 .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].rd_wq),
	 .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].wt_wq),
	 .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[0].event_lock),
	 },
	{
	 .dev_name = MLB_ASYNC_DEV_NAME,
	 .channel_type = MLB_CTYPE_ASYNC,
	 .channels = {
		      [0] = {
			     .reg_offset = MLB_ASYNC_TX_CHANN,
			     .buf_size = TX_CHANNEL_BUF_SIZE,
			     .buf_lock =
			     __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[0].
						buf_lock),
			     },
		      [1] = {
			     .reg_offset = MLB_ASYNC_RX_CHANN,
			     .buf_size = RX_CHANNEL_BUF_SIZE,
			     .buf_lock =
			     __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[1].
						buf_lock),
			     },
		      },
	 .on = ATOMIC_INIT(0),
	 .opencnt = ATOMIC_INIT(0),
	 .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].rd_wq),
	 .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].wt_wq),
	 .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[1].event_lock),
	 },
};

static struct regulator *reg_nvcc;	/* NVCC_MLB regulator */
static struct clk *mlb_clk;
static struct cdev mxc_mlb_dev;	/* chareset device */
static dev_t dev;
static struct class *mlb_class;	/* device class */
static struct device *class_dev;
static unsigned long mlb_base;	/* mlb module base address */
static unsigned int irq;
static unsigned long iram_base;
static __iomem void *iram_addr;

/*!
 * Initial the MLB module device
 */
static void mlb_dev_init(void)
{
	unsigned long dccr_val;
	unsigned long phyaddr;

	/* reset the MLB module */
	__raw_writel(MLB_DCCR_RESET, mlb_base + MLB_REG_DCCR);
	while (__raw_readl(mlb_base + MLB_REG_DCCR)
	       & MLB_DCCR_RESET) ;

	/*!
	 * Enable MLB device, disable loopback mode,
	 * set default fps to 512, set mlb device address to 0
	 */
	dccr_val = MLB_DCCR_EN;
	__raw_writel(dccr_val, mlb_base + MLB_REG_DCCR);

	/* disable all the system interrupt */
	__raw_writel(0x5F, mlb_base + MLB_REG_SMCR);

	/* write async, control tx/rx base address */
	phyaddr = _get_txchan(0).phy_head >> 16;
	__raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_CBCR);
	phyaddr = _get_txchan(1).phy_head >> 16;
	__raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_ABCR);

}

static void mlb_dev_exit(void)
{
	__raw_writel(0, mlb_base + MLB_REG_DCCR);
}

/*!
 * MLB receive start function
 *
 * load phy_head to next buf register to start next rx
 * here use single-packet buffer, set start=end
 */
static void mlb_start_rx(int cdev_id)
{
	struct mlb_channel_info *chinfo = &_get_rxchan(cdev_id);
	unsigned long next;

	next = chinfo->phy_head & 0xFFFC;
	/* load next buf */
	__raw_writel((next << 16) | next, mlb_base +
		     MLB_REG_CNBCRn + chinfo->reg_offset);
	/* set ready bit to start next rx */
	__raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn
		     + chinfo->reg_offset);
}

/*!
 * MLB transmit start function
 * make sure aquiring the rw buf_lock, when calling this
 */
static void mlb_start_tx(int cdev_id)
{
	struct mlb_channel_info *chinfo = &_get_txchan(cdev_id);
	unsigned long begin, end;

	begin = chinfo->phy_head;
	end = (chinfo->phy_head + chinfo->buf_ptr - chinfo->buf_head) & 0xFFFC;
	/* load next buf */
	__raw_writel((begin << 16) | end, mlb_base +
		     MLB_REG_CNBCRn + chinfo->reg_offset);
	/* set ready bit to start next tx */
	__raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn
		     + chinfo->reg_offset);
}

/*!
 * Enable the MLB channel
 */
static void mlb_channel_enable(int chan_dev_id, int on)
{
	unsigned long tx_regval = 0, rx_regval = 0;
	/*!
	 * setup the direction, enable, channel type,
	 * mode select, channel address and mask buf start
	 */
	if (on) {
		unsigned int ctype = mlb_devinfo[chan_dev_id].channel_type;
		tx_regval = MLB_CECR_CE | MLB_CECR_TR | MLB_CECR_MBS |
		    (ctype << MLB_CECR_CT_OFFSET) |
		    _get_txchan(chan_dev_id).address;
		rx_regval = MLB_CECR_CE | MLB_CECR_MBS |
		    (ctype << MLB_CECR_CT_OFFSET) |
		    _get_rxchan(chan_dev_id).address;

		atomic_set(&mlb_devinfo[chan_dev_id].on, 1);
	} else {
		atomic_set(&mlb_devinfo[chan_dev_id].on, 0);
	}

	/* update the rx/tx channel entry config */
	__raw_writel(tx_regval, mlb_base + MLB_REG_CECRn +
		     _get_txchan(chan_dev_id).reg_offset);
	__raw_writel(rx_regval, mlb_base + MLB_REG_CECRn +
		     _get_rxchan(chan_dev_id).reg_offset);

	if (on)
		mlb_start_rx(chan_dev_id);
}

/*!
 * MLB interrupt handler
 */
void mlb_tx_isr(int minor, unsigned int cis)
{
	struct mlb_channel_info *chinfo = &_get_txchan(minor);

	if (cis & MLB_CSCR_CBD) {
		/* buffer done, reset the buf_ptr */
		write_lock(&chinfo->buf_lock);
		chinfo->buf_ptr = chinfo->buf_head;
		write_unlock(&chinfo->buf_lock);
		/* wake up the writer */
		wake_up_interruptible(&mlb_devinfo[minor].wt_wq);
	}
}

void mlb_rx_isr(int minor, unsigned int cis)
{
	struct mlb_channel_info *chinfo = &_get_rxchan(minor);
	unsigned long end;
	unsigned int len;

	if (cis & MLB_CSCR_CBD) {

		int wpos, rpos;

		rpos = mlb_devinfo[minor].rdpos;
		wpos = mlb_devinfo[minor].wtpos;

		/* buffer done, get current buffer ptr */
		end =
		    __raw_readl(mlb_base + MLB_REG_CCBCRn + chinfo->reg_offset);
		end >>= 16;	/* end here is phy */
		len = end - (chinfo->phy_head & 0xFFFC);

		/*!
		 * copy packet from IRAM buf to ring buf.
		 * if the wpos++ == rpos, drop this packet
		 */
		if (((wpos + 1) % RX_RING_NODES) != rpos) {

#ifdef DEBUG
			if (mlb_devinfo[minor].channel_type == MLB_CTYPE_CTRL) {
				if (len > CTRL_PACKET_SIZE)
					pr_debug
					    ("mxc_mlb: ctrl packet"
					     "overflow\n");
			} else {
				if (len > ASYNC_PACKET_SIZE)
					pr_debug
					    ("mxc_mlb: async packet"
					     "overflow\n");
			}
#endif
			memcpy(mlb_devinfo[minor].rx_bufs[wpos].data,
			       (const void *)chinfo->buf_head, len);
			mlb_devinfo[minor].rx_bufs[wpos].size = len;

			/* update the ring wpos */
			mlb_devinfo[minor].wtpos = (wpos + 1) % RX_RING_NODES;

			/* wake up the reader */
			wake_up_interruptible(&mlb_devinfo[minor].rd_wq);

			pr_debug("recv package, len:%d, rdpos: %d, wtpos: %d\n",
				 len, rpos, mlb_devinfo[minor].wtpos);
		} else {
			pr_debug
			    ("drop package, due to no space, (%d,%d)\n",
			     rpos, mlb_devinfo[minor].wtpos);
		}

		/* start next rx */
		mlb_start_rx(minor);
	}
}

static irqreturn_t mlb_isr(int irq, void *dev_id)
{
	unsigned long int_status, sscr, tx_cis, rx_cis;
	struct mlb_dev_info *pdev;
	int minor;

	sscr = __raw_readl(mlb_base + MLB_REG_SSCR);
	pr_debug("mxc_mlb: system interrupt:%lx\n", sscr);
	__raw_writel(0x7F, mlb_base + MLB_REG_SSCR);

	int_status = __raw_readl(mlb_base + MLB_REG_CICR) & 0xFFFF;
	pr_debug("mxc_mlb: channel interrupt ids: %lx\n", int_status);

	for (minor = 0; minor < MLB_MINOR_DEVICES; minor++) {

		pdev = &mlb_devinfo[minor];
		tx_cis = rx_cis = 0;

		/* get tx channel interrupt status */
		if (int_status & (1 << (_get_txchan(minor).reg_offset >> 4)))
			tx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn
					     + _get_txchan(minor).reg_offset);
		/* get rx channel interrupt status */
		if (int_status & (1 << (_get_rxchan(minor).reg_offset >> 4)))
			rx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn
					     + _get_rxchan(minor).reg_offset);

		if (!tx_cis && !rx_cis)
			continue;

		pr_debug("tx/rx int status: 0x%08lx/0x%08lx\n", tx_cis, rx_cis);
		/* fill exception event */
		spin_lock(&pdev->event_lock);
		pdev->ex_event |= tx_cis & 0x303;
		pdev->ex_event |= (rx_cis & 0x303) << 16;
		spin_unlock(&pdev->event_lock);

		/* clear the interrupt status */
		__raw_writel(tx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn
			     + _get_txchan(minor).reg_offset);
		__raw_writel(rx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn
			     + _get_rxchan(minor).reg_offset);

		/* handel tx channel */
		if (tx_cis)
			mlb_tx_isr(minor, tx_cis);
		/* handle rx channel */
		if (rx_cis)
			mlb_rx_isr(minor, rx_cis);

	}

	return IRQ_HANDLED;
}

static int mxc_mlb_open(struct inode *inode, struct file *filp)
{
	int minor;

	minor = MINOR(inode->i_rdev);

	if (minor < 0 || minor >= MLB_MINOR_DEVICES)
		return -ENODEV;

	/* open for each channel device */
	if (atomic_cmpxchg(&mlb_devinfo[minor].opencnt, 0, 1) != 0)
		return -EBUSY;

	/* reset the buffer read/write ptr */
	_get_txchan(minor).buf_ptr = _get_txchan(minor).buf_head;
	_get_rxchan(minor).buf_ptr = _get_rxchan(minor).buf_head;
	mlb_devinfo[minor].rdpos = mlb_devinfo[minor].wtpos = 0;
	mlb_devinfo[minor].ex_event = 0;

	return 0;
}

static int mxc_mlb_release(struct inode *inode, struct file *filp)
{
	int minor;

	minor = MINOR(inode->i_rdev);

	/* clear channel settings and info */
	mlb_channel_enable(minor, 0);

	/* decrease the open count */
	atomic_set(&mlb_devinfo[minor].opencnt, 0);

	return 0;
}

static int mxc_mlb_ioctl(struct inode *inode, struct file *filp,
			 unsigned int cmd, unsigned long arg)
{
	void __user *argp = (void __user *)arg;
	unsigned long flags, event;
	int minor;

	minor = MINOR(inode->i_rdev);

	switch (cmd) {

	case MLB_CHAN_SETADDR:
		{
			unsigned int caddr;
			/* get channel address from user space */
			if (copy_from_user(&caddr, argp, sizeof(caddr))) {
				pr_err("mxc_mlb: copy from user failed\n");
				return -EFAULT;
			}
			_get_txchan(minor).address = (caddr >> 16) & 0xFFFF;
			_get_rxchan(minor).address = caddr & 0xFFFF;
			break;
		}

	case MLB_CHAN_STARTUP:
		if (atomic_read(&mlb_devinfo[minor].on)) {
			pr_debug("mxc_mlb: channel areadly startup\n");
			break;
		}
		mlb_channel_enable(minor, 1);
		break;
	case MLB_CHAN_SHUTDOWN:
		if (atomic_read(&mlb_devinfo[minor].on) == 0) {
			pr_debug("mxc_mlb: channel areadly shutdown\n");
			break;
		}
		mlb_channel_enable(minor, 0);
		break;
	case MLB_CHAN_GETEVENT:
		/* get and clear the ex_event */
		spin_lock_irqsave(&mlb_devinfo[minor].event_lock, flags);
		event = mlb_devinfo[minor].ex_event;
		mlb_devinfo[minor].ex_event = 0;
		spin_unlock_irqrestore(&mlb_devinfo[minor].event_lock, flags);

		if (event) {
			if (copy_to_user(argp, &event, sizeof(event))) {
				pr_err("mxc_mlb: copy to user failed\n");
				return -EFAULT;
			}
		} else {
			pr_debug("mxc_mlb: no exception event now\n");
			return -EAGAIN;
		}
		break;
	case MLB_SET_FPS:
		{
			unsigned int fps;
			unsigned long dccr_val;

			/* get fps from user space */
			if (copy_from_user(&fps, argp, sizeof(fps))) {
				pr_err("mxc_mlb: copy from user failed\n");
				return -EFAULT;
			}

			/* check fps value */
			if (fps != 256 && fps != 512 && fps != 1024) {
				pr_debug("mxc_mlb: invalid fps argument\n");
				return -EINVAL;
			}

			dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR);
			dccr_val &= ~(0x3 << MLB_DCCR_FS_OFFSET);
			dccr_val |= (fps >> 9) << MLB_DCCR_FS_OFFSET;
			__raw_writel(dccr_val, mlb_base + MLB_REG_DCCR);
			break;
		}

	case MLB_GET_VER:
		{
			unsigned long version;

			/* get MLB device module version */
			version = __raw_readl(mlb_base + MLB_REG_VCCR);

			if (copy_to_user(argp, &version, sizeof(version))) {
				pr_err("mxc_mlb: copy to user failed\n");
				return -EFAULT;
			}
			break;
		}

	case MLB_SET_DEVADDR:
		{
			unsigned long dccr_val;
			unsigned char devaddr;

			/* get MLB device address from user space */
			if (copy_from_user
			    (&devaddr, argp, sizeof(unsigned char))) {
				pr_err("mxc_mlb: copy from user failed\n");
				return -EFAULT;
			}

			dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR);
			dccr_val &= ~0xFF;
			dccr_val |= devaddr;
			__raw_writel(dccr_val, mlb_base + MLB_REG_DCCR);

			break;
		}
	default:
		pr_info("mxc_mlb: Invalid ioctl command\n");
		return -EINVAL;
	}

	return 0;
}

/*!
 * MLB read routine
 *
 * Read the current received data from queued buffer,
 * and free this buffer for hw to fill ingress data.
 */
static ssize_t mxc_mlb_read(struct file *filp, char __user *buf,
			    size_t count, loff_t *f_pos)
{
	int minor, ret;
	int size, rdpos;
	struct mlb_rx_ringnode *rxbuf;

	minor = MINOR(filp->f_dentry->d_inode->i_rdev);

	rdpos = mlb_devinfo[minor].rdpos;
	rxbuf = mlb_devinfo[minor].rx_bufs;

	/* check the current rx buffer is available or not */
	if (rdpos == mlb_devinfo[minor].wtpos) {
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		/* if !O_NONBLOCK, we wait for recv packet */
		ret = wait_event_interruptible(mlb_devinfo[minor].rd_wq,
						(mlb_devinfo[minor].wtpos !=
						rdpos));
		if (ret < 0)
			return ret;
	}

	size = rxbuf[rdpos].size;
	if (size > count) {
		/* the user buffer is too small */
		pr_warning
		    ("mxc_mlb: received data size is bigger than count\n");
		return -EINVAL;
	}

	/* copy rx buffer data to user buffer */
	if (copy_to_user(buf, rxbuf[rdpos].data, size)) {
		pr_err("mxc_mlb: copy from user failed\n");
		return -EFAULT;
	}

	/* update the read ptr */
	mlb_devinfo[minor].rdpos = (rdpos + 1) % RX_RING_NODES;

	*f_pos = 0;

	return size;
}

/*!
 * MLB write routine
 *
 * Copy the user data to tx channel buffer,
 * and prepare the channel current/next buffer ptr.
 */
static ssize_t mxc_mlb_write(struct file *filp, const char __user *buf,
			     size_t count, loff_t *f_pos)
{
	int minor;
	unsigned long flags;
	DEFINE_WAIT(__wait);
	int ret;

	minor = MINOR(filp->f_dentry->d_inode->i_rdev);

	if (count > _get_txchan(minor).buf_size) {
		/* too many data to write */
		pr_warning("mxc_mlb: overflow write data\n");
		return -EFBIG;
	}

	*f_pos = 0;

	/* check the current tx buffer is used or not */
	write_lock_irqsave(&_get_txchan(minor).buf_lock, flags);
	if (_get_txchan(minor).buf_ptr != _get_txchan(minor).buf_head) {
		write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags);

		/* there's already some datas being transmit now */
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;

		/* if !O_NONBLOCK, we wait for transmit finish */
		for (;;) {
			prepare_to_wait(&mlb_devinfo[minor].wt_wq,
					&__wait, TASK_INTERRUPTIBLE);

			write_lock_irqsave(&_get_txchan(minor).buf_lock, flags);
			if (_get_txchan(minor).buf_ptr ==
			    _get_txchan(minor).buf_head)
				break;

			write_unlock_irqrestore(&_get_txchan(minor).buf_lock,
						flags);
			if (!signal_pending(current)) {
				schedule();
				continue;
			}
			return -ERESTARTSYS;
		}
		finish_wait(&mlb_devinfo[minor].wt_wq, &__wait);
	}

	/* copy user buffer to tx buffer */
	if (copy_from_user((void *)_get_txchan(minor).buf_ptr, buf, count)) {
		pr_err("mxc_mlb: copy from user failed\n");
		ret = -EFAULT;
		goto out;
	}
	_get_txchan(minor).buf_ptr += count;

	/* set current/next buffer start/end */
	mlb_start_tx(minor);

	ret = count;

out:
	write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags);
	return ret;
}

static unsigned int mxc_mlb_poll(struct file *filp,
				 struct poll_table_struct *wait)
{
	int minor;
	unsigned int ret = 0;
	unsigned long flags;

	minor = MINOR(filp->f_dentry->d_inode->i_rdev);

	poll_wait(filp, &mlb_devinfo[minor].rd_wq, wait);
	poll_wait(filp, &mlb_devinfo[minor].wt_wq, wait);

	/* check the tx buffer is avaiable or not */
	read_lock_irqsave(&_get_txchan(minor).buf_lock, flags);
	if (_get_txchan(minor).buf_ptr == _get_txchan(minor).buf_head)
		ret |= POLLOUT | POLLWRNORM;
	read_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags);

	/* check the rx buffer filled or not */
	if (mlb_devinfo[minor].rdpos != mlb_devinfo[minor].wtpos)
		ret |= POLLIN | POLLRDNORM;

	/* check the exception event */
	if (mlb_devinfo[minor].ex_event)
		ret |= POLLIN | POLLRDNORM;

	return ret;
}

/*!
 * char dev file operations structure
 */
static struct file_operations mxc_mlb_fops = {

	.owner = THIS_MODULE,
	.open = mxc_mlb_open,
	.release = mxc_mlb_release,
	.ioctl = mxc_mlb_ioctl,
	.poll = mxc_mlb_poll,
	.read = mxc_mlb_read,
	.write = mxc_mlb_write,
};

/*!
 * This function is called whenever the MLB device is detected.
 */
static int __devinit mxc_mlb_probe(struct platform_device *pdev)
{
	int ret, mlb_major, i, j;
	struct mxc_mlb_platform_data *plat_data;
	struct resource *res;
	void __iomem *base, *bufaddr;
	unsigned long phyaddr;

	/* malloc the Rx ring buffer firstly */
	for (i = 0; i < MLB_MINOR_DEVICES; i++) {
		char *buf;
		int bufsize;

		if (mlb_devinfo[i].channel_type == MLB_CTYPE_ASYNC)
			bufsize = ASYNC_PACKET_SIZE;
		else
			bufsize = CTRL_PACKET_SIZE;

		buf = kmalloc(bufsize * RX_RING_NODES, GFP_KERNEL);
		if (buf == NULL) {
			ret = -ENOMEM;
			dev_err(&pdev->dev, "can not alloc rx buffers\n");
			goto err4;
		}
		for (j = 0; j < RX_RING_NODES; j++) {
			mlb_devinfo[i].rx_bufs[j].data = buf;
			buf += bufsize;
		}
	}

	/**
	 * Register MLB lld as two character devices
	 * One for Packet date channel, the other for control data channel
	 */
	ret = alloc_chrdev_region(&dev, 0, MLB_MINOR_DEVICES, "mxc_mlb");
	mlb_major = MAJOR(dev);

	if (ret < 0) {
		dev_err(&pdev->dev, "can't get major %d\n", mlb_major);
		goto err3;
	}

	cdev_init(&mxc_mlb_dev, &mxc_mlb_fops);
	mxc_mlb_dev.owner = THIS_MODULE;

	ret = cdev_add(&mxc_mlb_dev, dev, MLB_MINOR_DEVICES);
	if (ret) {
		dev_err(&pdev->dev, "can't add cdev\n");
		goto err2;
	}

	/* create class and device for udev information */
	mlb_class = class_create(THIS_MODULE, "mlb");
	if (IS_ERR(mlb_class)) {
		dev_err(&pdev->dev, "failed to create mlb class\n");
		ret = -ENOMEM;
		goto err2;
	}

	for (i = 0; i < MLB_MINOR_DEVICES; i++) {

		class_dev = device_create(mlb_class, NULL, MKDEV(mlb_major, i),
					  NULL, mlb_devinfo[i].dev_name);
		if (IS_ERR(class_dev)) {
			dev_err(&pdev->dev, "failed to create mlb %s"
				" class device\n", mlb_devinfo[i].dev_name);
			ret = -ENOMEM;
			goto err1;
		}
	}

	/* get irq line */
	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "No mlb irq line provided\n");
		goto err1;
	}

	irq = res->start;
	/* request irq */
	if (request_irq(irq, mlb_isr, 0, "mlb", NULL)) {
		dev_err(&pdev->dev, "failed to request irq\n");
		ret = -EBUSY;
		goto err1;
	}

	/* ioremap from phy mlb to kernel space */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "No mlb base address provided\n");
		goto err0;
	}

	base = ioremap(res->start, res->end - res->start);
	dev_dbg(&pdev->dev, "mapped mlb base address: 0x%08x\n",
		(unsigned int)base);

	if (base == NULL) {
		dev_err(&pdev->dev, "failed to do ioremap with mlb base\n");
		goto err0;
	}
	mlb_base = (unsigned long)base;

	/*!
	 * get rx/tx buffer address from platform data
	 * make sure the buf_address is 4bytes aligned
	 *
	 * -------------------   <-- plat_data->buf_address
	 * | minor 0 tx buf  |
	 *  -----------------
	 * | minor 0 rx buf  |
	 *  -----------------
	 * |    ....         |
	 *  -----------------
	 * | minor n tx buf  |
	 *  -----------------
	 * | minor n rx buf  |
	 * -------------------
	 */

	plat_data = (struct mxc_mlb_platform_data *)pdev->dev.platform_data;

	bufaddr = iram_addr = iram_alloc(MLB_IRAM_SIZE, &iram_base);
	phyaddr = iram_base;

	for (i = 0; i < MLB_MINOR_DEVICES; i++) {
		/* set the virtual and physical buf head address */
		_get_txchan(i).buf_head = bufaddr;
		_get_txchan(i).phy_head = phyaddr;

		bufaddr += TX_CHANNEL_BUF_SIZE;
		phyaddr += TX_CHANNEL_BUF_SIZE;

		_get_rxchan(i).buf_head = (unsigned long)bufaddr;
		_get_rxchan(i).phy_head = phyaddr;

		bufaddr += RX_CHANNEL_BUF_SIZE;
		phyaddr += RX_CHANNEL_BUF_SIZE;

		dev_dbg(&pdev->dev, "phy_head: tx(%lx), rx(%lx)\n",
			_get_txchan(i).phy_head, _get_rxchan(i).phy_head);
		dev_dbg(&pdev->dev, "buf_head: tx(%lx), rx(%lx)\n",
			_get_txchan(i).buf_head, _get_rxchan(i).buf_head);
	}

	/* enable GPIO */
	gpio_mlb_active();

	/* power on MLB */
	reg_nvcc = regulator_get(&pdev->dev, plat_data->reg_nvcc);
	/* set MAX LDO6 for NVCC to 2.5V */
	regulator_set_voltage(reg_nvcc, 2500000, 2500000);
	regulator_enable(reg_nvcc);

	/* enable clock */
	mlb_clk = clk_get(&pdev->dev, plat_data->mlb_clk);
	clk_enable(mlb_clk);

	/* initial MLB module */
	mlb_dev_init();

	return 0;

err0:
	free_irq(irq, NULL);
err1:
	for (--i; i >= 0; i--)
		device_destroy(mlb_class, MKDEV(mlb_major, i));

	class_destroy(mlb_class);
err2:
	cdev_del(&mxc_mlb_dev);
err3:
	unregister_chrdev_region(dev, MLB_MINOR_DEVICES);
err4:
	for (i = 0; i < MLB_MINOR_DEVICES; i++)
		kfree(mlb_devinfo[i].rx_bufs[0].data);

	return ret;
}

static int __devexit mxc_mlb_remove(struct platform_device *pdev)
{
	int i;

	mlb_dev_exit();

	/* disable mlb clock */
	clk_disable(mlb_clk);
	clk_put(mlb_clk);

	/* disable mlb power */
	regulator_disable(reg_nvcc);
	regulator_put(reg_nvcc);

	/* inactive GPIO */
	gpio_mlb_inactive();

	iram_free(iram_base, MLB_IRAM_SIZE);

	/* iounmap */
	iounmap((void *)mlb_base);

	free_irq(irq, NULL);

	/* destroy mlb device class */
	for (i = MLB_MINOR_DEVICES - 1; i >= 0; i--)
		device_destroy(mlb_class, MKDEV(MAJOR(dev), i));
	class_destroy(mlb_class);

	/* Unregister the two MLB devices */
	cdev_del(&mxc_mlb_dev);
	unregister_chrdev_region(dev, MLB_MINOR_DEVICES);

	for (i = 0; i < MLB_MINOR_DEVICES; i++)
		kfree(mlb_devinfo[i].rx_bufs[0].data);

	return 0;
}

#ifdef CONFIG_PM
static int mxc_mlb_suspend(struct platform_device *pdev, pm_message_t state)
{
	return 0;
}

static int mxc_mlb_resume(struct platform_device *pdev)
{
	return 0;
}
#else
#define mxc_mlb_suspend NULL
#define mxc_mlb_resume NULL
#endif

/*!
 * platform driver structure for MLB
 */
static struct platform_driver mxc_mlb_driver = {
	.driver = {
		   .name = "mxc_mlb"},
	.probe = mxc_mlb_probe,
	.remove = __devexit_p(mxc_mlb_remove),
	.suspend = mxc_mlb_suspend,
	.resume = mxc_mlb_resume,
};

static int __init mxc_mlb_init(void)
{
	return platform_driver_register(&mxc_mlb_driver);
}

static void __exit mxc_mlb_exit(void)
{
	platform_driver_unregister(&mxc_mlb_driver);
}

module_init(mxc_mlb_init);
module_exit(mxc_mlb_exit);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MLB low level driver");
MODULE_LICENSE("GPL");
