/*
 * sas: a simple 9-bit serial driver suitable for
 * us in implementing the Slot Accounting System (SAS)
 * protocol.
 *
 * - The driver exposes character devices /dev/sas0-N,
 * which use the 9-bit support of the UARTs on i.MX
 * SOCs to implement the framing portion of the protocol.
 *
 * - Each write() call is translated into a message with
 * MARK parity on the first byte and SPACE parity for the
 * remaining bytes of the message.
 *
 * Received data is parsed according to the same protocol,
 * such that a "message" is defined as a set of incoming
 * data that starts with a byte with MARK parity and
 * terminates when either a timeout occurs or another
 * byte with MARK parity is received
 *
 * - Each read() call returns zero or one complete
 * messages.
 *
 * - Improperly framed incoming messages are reported
 * through printk but are not returned to userspace.
 * e.g. if a timeout occurs to terminate a message and
 * is followed by data with SPACE parity, the characters
 * will be dropped.
 *
 * The driver supports poll().
 *
 *	POLLIN is signalled when a complete message is available.
 *
 *	POLLOUT is signalled when space for a maximum sized message
 *		is available (device tree maxtxmsg).
 *
 * Copyright (C) 2015, Boundary Devices
 */

#define DEBUG

#include <linux/cdev.h>
#include <linux/circ_buf.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/wait.h>

/* Register definitions */
#define URXD0 0x0  /* Receiver Register */
#define URTX0 0x40 /* Transmitter Register */
#define UCR1  0x80 /* Control Register 1 */
#define UCR2  0x84 /* Control Register 2 */
#define UCR3  0x88 /* Control Register 3 */
#define UCR4  0x8c /* Control Register 4 */
#define UFCR  0x90 /* FIFO Control Register */
#define USR1  0x94 /* Status Register 1 */
#define USR2  0x98 /* Status Register 2 */
#define UESC  0x9c /* Escape Character Register */
#define UTIM  0xa0 /* Escape Timer Register */
#define UBIR  0xa4 /* BRM Incremental Register */
#define UBMR  0xa8 /* BRM Modulator Register */
#define UBRC  0xac /* Baud Rate Count Register */
#define IMX21_ONEMS 0xb0 /* One Millisecond register */
#define IMX1_UTS 0xd0 /* UART Test Register on i.mx1 */
#define IMX21_UTS 0xb4 /* UART Test Register on all other i.mx*/
#define UMCR  0xb8
#define UMCR_MDEN 1		/* 9-bit/Multidrop enable */
#define UMCR_TXB8 (1<<2)	/* parity bit goes here */

/* UART Control Register Bit Fields.*/
#define URXD_CHARRDY	(1<<15)
#define URXD_ERR	(1<<14)
#define URXD_OVRRUN	(1<<13)
#define URXD_FRMERR	(1<<12)
#define URXD_BRK	(1<<11)
#define URXD_PRERR	(1<<10)
#define UCR1_ADEN	(1<<15) /* Auto detect interrupt */
#define UCR1_ADBR	(1<<14) /* Auto detect baud rate */
#define UCR1_TRDYEN	(1<<13) /* Transmitter ready interrupt enable */
#define UCR1_IDEN	(1<<12) /* Idle condition interrupt */
#define UCR1_ICD_REG(x) (((x) & 3) << 10) /* idle condition detect */
#define UCR1_RRDYEN	(1<<9)	/* Recv ready interrupt enable */
#define UCR1_RDMAEN	(1<<8)	/* Recv ready DMA enable */
#define UCR1_IREN	(1<<7)	/* Infrared interface enable */
#define UCR1_TXMPTYEN	(1<<6)	/* Transimitter empty interrupt enable */
#define UCR1_RTSDEN	(1<<5)	/* RTS delta interrupt enable */
#define UCR1_SNDBRK	(1<<4)	/* Send break */
#define UCR1_TDMAEN	(1<<3)	/* Transmitter ready DMA enable */
#define IMX1_UCR1_UARTCLKEN (1<<2) /* UART clock enabled, i.mx1 only */
#define UCR1_ATDMAEN    (1<<2)  /* Aging DMA Timer Enable */
#define UCR1_DOZE	(1<<1)	/* Doze */
#define UCR1_UARTEN	(1<<0)	/* UART enabled */
#define UCR2_ESCI	(1<<15)	/* Escape seq interrupt enable */
#define UCR2_IRTS	(1<<14)	/* Ignore RTS pin */
#define UCR2_CTSC	(1<<13)	/* CTS pin control */
#define UCR2_CTS	(1<<12)	/* Clear to send */
#define UCR2_ESCEN	(1<<11)	/* Escape enable */
#define UCR2_PREN	(1<<8)	/* Parity enable */
#define UCR2_PROE	(1<<7)	/* Parity odd/even */
#define UCR2_STPB	(1<<6)	/* Stop */
#define UCR2_WS		(1<<5)	/* Word size */
#define UCR2_RTSEN	(1<<4)	/* Request to send interrupt enable */
#define UCR2_ATEN	(1<<3)	/* Aging Timer Enable */
#define UCR2_TXEN	(1<<2)	/* Transmitter enabled */
#define UCR2_RXEN	(1<<1)	/* Receiver enabled */
#define UCR2_SRST	(1<<0)	/* SW reset */
#define UCR3_DTREN	(1<<13) /* DTR interrupt enable */
#define UCR3_PARERREN	(1<<12) /* Parity enable */
#define UCR3_FRAERREN	(1<<11) /* Frame error interrupt enable */
#define UCR3_DSR	(1<<10) /* Data set ready */
#define UCR3_DCD	(1<<9)	/* Data carrier detect */
#define UCR3_RI		(1<<8)	/* Ring indicator */
#define UCR3_ADNIMP	(1<<7)	/* Autobaud Detection Not Improved */
#define UCR3_RXDSEN	(1<<6)	/* Receive status interrupt enable */
#define UCR3_AIRINTEN	(1<<5)	/* Async IR wake interrupt enable */
#define UCR3_AWAKEN	(1<<4)	/* Async wake interrupt enable */
#define IMX21_UCR3_RXDMUXSEL	(1<<2)	/* RXD Muxed Input Select */
#define UCR3_INVT	(1<<1)	/* Inverted Infrared transmission */
#define UCR3_BPEN	(1<<0)	/* Preset registers enable */
#define UCR4_CTSTL_SHF	10	/* CTS trigger level shift */
#define UCR4_CTSTL_MASK	0x3F	/* CTS trigger is 6 bits wide */
#define UCR4_INVR	(1<<9)	/* Inverted infrared reception */
#define UCR4_ENIRI	(1<<8)	/* Serial infrared interrupt enable */
#define UCR4_WKEN	(1<<7)	/* Wake interrupt enable */
#define UCR4_REF16	(1<<6)	/* Ref freq 16 MHz */
#define UCR4_IDDMAEN    (1<<6)  /* DMA IDLE Condition Detected */
#define UCR4_IRSC	(1<<5)	/* IR special case */
#define UCR4_TCEN	(1<<3)	/* Transmit complete interrupt enable */
#define UCR4_BKEN	(1<<2)	/* Break condition interrupt enable */
#define UCR4_OREN	(1<<1)	/* Receiver overrun interrupt enable */
#define UCR4_DREN	(1<<0)	/* Recv data ready interrupt enable */
#define UFCR_RXTL_SHF	0	/* Receiver trigger level shift */
#define UFCR_DCEDTE	(1<<6)	/* DCE/DTE mode select */
#define UFCR_RFDIV	(7<<7)	/* Reference freq divider mask */
#define UFCR_RFDIV_REG(x)	(((x) < 7 ? 6 - (x) : 6) << 7)
#define UFCR_TXTL_SHF	10	/* Transmitter trigger level shift */
#define USR1_PARITYERR	(1<<15) /* Parity error interrupt flag */
#define USR1_RTSS	(1<<14) /* RTS pin status */
#define USR1_TRDY	(1<<13) /* Transmitter ready interrupt/dma flag */
#define USR1_RTSD	(1<<12) /* RTS delta */
#define USR1_ESCF	(1<<11) /* Escape seq interrupt flag */
#define USR1_FRAMERR	(1<<10) /* Frame error interrupt flag */
#define USR1_RRDY	(1<<9)	 /* Receiver ready interrupt/dma flag */
#define USR1_TIMEOUT	(1<<7)	 /* Receive timeout interrupt status */
#define USR1_RXDS	 (1<<6)	 /* Receiver idle interrupt flag */
#define USR1_AIRINT	 (1<<5)	 /* Async IR wake interrupt flag */
#define USR1_AWAKE	 (1<<4)	 /* Aysnc wake interrupt flag */
#define USR2_ADET	 (1<<15) /* Auto baud rate detect complete */
#define USR2_TXFE	 (1<<14) /* Transmit buffer FIFO empty */
#define USR2_DTRF	 (1<<13) /* DTR edge interrupt flag */
#define USR2_IDLE	 (1<<12) /* Idle condition */
#define USR2_IRINT	 (1<<8)	 /* Serial infrared interrupt flag */
#define USR2_WAKE	 (1<<7)	 /* Wake */
#define USR2_RTSF	 (1<<4)	 /* RTS edge interrupt flag */
#define USR2_TXDC	 (1<<3)	 /* Transmitter complete */
#define USR2_BRCD	 (1<<2)	 /* Break condition */
#define USR2_ORE	(1<<1)	 /* Overrun error */
#define USR2_RDR	(1<<0)	 /* Recv data ready */
#define UTS_FRCPERR	(1<<13) /* Force parity error */
#define UTS_LOOP	(1<<12)	 /* Loop tx and rx */
#define UTS_TXEMPTY	 (1<<6)	 /* TxFIFO empty */
#define UTS_RXEMPTY	 (1<<5)	 /* RxFIFO empty */
#define UTS_TXFULL	 (1<<4)	 /* TxFIFO full */
#define UTS_RXFULL	 (1<<3)	 /* RxFIFO full */
#define UTS_SOFTRST	 (1<<0)	 /* Software reset */

static int sas_major;
static dev_t devnum;
static struct class *sas_class;

#define DRIVER_NAME "sas"

#define MS_TO_NS(msec)	((msec) * 1000 * 1000)

static void hrtimer_mod(struct hrtimer *timer, u32 ms)
{
	ktime_t ktime = ktime_set(0, MS_TO_NS(ms));
	hrtimer_try_to_cancel(timer);
	hrtimer_start(timer, ktime, HRTIMER_MODE_REL);
}

struct msg_t {
	int start;
	int end;
};

/*
 * Device structure, to keep track of everything device-specific here
 */
struct sas_dev {
	struct cdev cdev;
	struct device *chrdev;
	struct platform_device *pdev;
	void __iomem *base;
	wait_queue_head_t queue;
	spinlock_t lock; /* only one IRQ at a time */
	struct mutex rx_lock; /* only one reader */
	struct mutex tx_lock; /* only one writer */
	struct hrtimer timer;
	int open_count;

	struct clk *clk_ipg;
	struct clk *clk_per;
	int irq;
	int rxirq;
	int txirq;
	u32 baud;
	u32 interbyte_delay;
	u32 rxbufsize;
	u32 maxrxmsgs;
	u32 txbufsize;
	u32 maxtxmsg;
	u32 flush_on_mark;
	struct circ_buf rxbuf;
	struct msg_t *rxmsgs;
	u32 rxmsgadd;
	u32 rxmsgtake;
	struct circ_buf txbuf;
	u8 *txpbuf; /* parity for outbound characters */
	u8 last_parity;
};

#define CIRC_NEXT(index, size) ((index + 1) & (size - 1))
#define CIRC_PREV(index, size) ((index - 1) & (size - 1))

/* end of last messsage added */
#define PREVMSGEND(dev) (dev->rxmsgs[CIRC_PREV(dev->rxmsgadd, \
					       dev->maxrxmsgs)].end)

/* start of next message */
#define NEXTMSGSTART(dev) (dev->rxmsgs[dev->rxmsgadd].start)

static void flush_msg(struct sas_dev *dev)
{
	if (dev->rxbuf.head != NEXTMSGSTART(dev)) {
		int nextmsg = CIRC_NEXT(dev->rxmsgadd, dev->maxrxmsgs);
		int end = CIRC_PREV(dev->rxbuf.head, dev->rxbufsize);
		if (dev->rxmsgtake != nextmsg) {
			dev->rxmsgs[dev->rxmsgadd].end = end;
			dev->rxmsgadd = nextmsg;
			dev->rxmsgs[nextmsg].start = dev->rxbuf.head;
		} else {
			/* append to the previous message, but whine */
			dev_err(&dev->pdev->dev, "message overflow\n");
			PREVMSGEND(dev) = end;
		}
		wake_up(&dev->queue);
	}
}

static enum hrtimer_restart rx_timer(struct hrtimer *timer)
{
	struct sas_dev *dev = container_of(timer, struct sas_dev, timer);
	flush_msg(dev);
	return HRTIMER_NORESTART;
}

static ssize_t sas_read
	(struct file *file, char __user *buf,
	 size_t count, loff_t *ppos)
{
	struct sas_dev *dev = (struct sas_dev *)file->private_data;
	ssize_t numread = 0;

	mutex_lock(&dev->rx_lock);
	if (dev->rxmsgadd != dev->rxmsgtake) {
		struct msg_t *msg = dev->rxmsgs + dev->rxmsgtake;
		int start = msg->start;
		int firstseg;
		int msgleft = (msg->end - msg->start + 1)
				& (dev->rxbufsize - 1);
		if (msgleft > count) {
			numread = -ENOBUFS;
			goto out;
		}
		firstseg = dev->rxbufsize - start;
		if (firstseg > msgleft)
			firstseg = msgleft;
		if (copy_to_user(buf, dev->rxbuf.buf+start, firstseg)) {
			numread = -EFAULT;
			goto out;
		}
		count = msgleft;
		msgleft -= firstseg;
		numread = firstseg;
		if (0 < msgleft) {
			/* wrap: need to copy a second segment */
			buf += firstseg;
			if (copy_to_user(buf, dev->rxbuf.buf+0, msgleft)) {
				numread = -EFAULT;
				goto out;
			}
			dev->rxbuf.tail = msgleft;
			numread += msgleft;
		} else {
			dev->rxbuf.tail = start + firstseg;
		}
		dev->rxmsgtake = CIRC_NEXT(dev->rxmsgtake, dev->maxrxmsgs);
	} /* have a message */
out:
	mutex_unlock(&dev->rx_lock);
	return numread;
}

static ssize_t sas_write(struct file *file, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	int written = 0;
	u32 reg;
	struct sas_dev *dev = (struct sas_dev *)file->private_data;

	mutex_lock(&dev->tx_lock);
	if (CIRC_SPACE(dev->txbuf.head,
		       dev->txbuf.tail,
		       dev->txbufsize) >= count) {
		int start = dev->txbuf.head;
		int firstseg = dev->txbufsize - start;
		if (firstseg > count)
			firstseg = count;
		if (copy_from_user(dev->txbuf.buf+start, buf, firstseg)) {
			written = -EFAULT;
		} else if (count > firstseg) {
			/* wrap: copy second segment */

			int left = count - firstseg;
			buf += firstseg;
			if (copy_from_user(dev->txbuf.buf, buf, left))
				written = -EFAULT;
		}
		if (0 == written) {
			u32 nextbyte = start / 8;
			u32 const pbytemask = (dev->txbufsize / 8) - 1;
			u8 mask = 1 << (start & 7);
			u8 byteval = dev->txpbuf[start/8] | mask;
			mask = ~(mask << 1);
			written = 1;
			while (--count) {
				if ('\xff' == mask) {
					dev->txpbuf[nextbyte] = byteval;
					nextbyte = (nextbyte + 1) & pbytemask;
					byteval = dev->txpbuf[nextbyte];
					mask = (u8)~1;
				}
				byteval &= mask;
				mask = (mask << 1) | 1;
				written++;
			}
			dev->txpbuf[nextbyte] = byteval;
			dev->txbuf.head = (dev->txbuf.head + written)
						& (dev->txbufsize - 1);
			reg = readl(dev->base + UCR1);
			reg |= UCR1_TRDYEN;
			writel(reg, dev->base + UCR1);
		}
	}

	mutex_unlock(&dev->tx_lock);
	return written;
}

static unsigned int sas_poll(struct file *file, struct poll_table_struct *table)
{
	unsigned int flags = 0;
	struct sas_dev *dev = (struct sas_dev *)file->private_data;
	if (!dev)
		return -EINVAL;

	poll_wait(file, &dev->queue, table);

	if (dev->rxbuf.head != dev->rxbuf.tail)
		flags |= POLLIN | POLLRDNORM;
	if (CIRC_SPACE(dev->txbuf.head,
		       dev->txbuf.tail,
		       dev->txbufsize) >= dev->maxtxmsg) {
		flags |= POLLOUT;
	}
	return flags;
}

static void sas_rxint(struct sas_dev *dev)
{
	int space = CIRC_SPACE(dev->rxbuf.head,
			       dev->rxbuf.tail,
			       dev->rxbufsize);
	while (space >= 3) {
		u32 in = readl(dev->base + URXD0);
		if (!(in & URXD_CHARRDY))
			break;
		if (in & URXD_PRERR) {
			/*
			 * if a part of a message is present, terminate it
			 * and notify userspace
			 */
			if (dev->flush_on_mark)
				flush_msg(dev);
			dev->rxbuf.buf[dev->rxbuf.head] = 0xff;
			dev->rxbuf.head = CIRC_NEXT(dev->rxbuf.head,
						    dev->rxbufsize);
			dev->rxbuf.buf[dev->rxbuf.head] = 0;
			dev->rxbuf.head = CIRC_NEXT(dev->rxbuf.head,
						    dev->rxbufsize);
			dev->rxbuf.buf[dev->rxbuf.head] = in;
			dev->rxbuf.head = CIRC_NEXT(dev->rxbuf.head,
						    dev->rxbufsize);

			space -= 3;
		} else {
			dev->rxbuf.buf[dev->rxbuf.head] = in;
			dev->rxbuf.head = CIRC_NEXT(dev->rxbuf.head,
						    dev->rxbufsize);
			space--;
		}

		hrtimer_mod(&dev->timer, dev->interbyte_delay);
		if (!(readl(dev->base + USR2) & USR2_RDR))
			break;
	}
	if (3 >= space) {
		dev_err(&dev->pdev->dev, "overrun");
		while (readl(dev->base + URXD0) & URXD_CHARRDY)
			;
	}
}

static void sas_txint(struct sas_dev *dev)
{
	u32 reg;
	while (CIRC_CNT(dev->txbuf.head,
			dev->txbuf.tail,
			dev->txbufsize) &&
			!(readl(dev->base + IMX21_UTS) & UTS_TXFULL)) {
		u8 shift = dev->txbuf.tail & 7;
		u8 mask = 1 << shift;
		u8 parity = (dev->txpbuf[dev->txbuf.tail/8] & mask);
		if (parity != dev->last_parity) {
			reg = readl(dev->base + USR2);
			if (!(reg & USR2_TXDC)) {
				/* wait for shift register empty */
				reg = readl(dev->base + UCR1);
				if ((reg & (UCR1_TRDYEN | UCR1_TXMPTYEN))
					!= (UCR1_TRDYEN | UCR1_TXMPTYEN)) {
					/* only interrupt on empty */
					reg &= ~UCR1_TRDYEN;
					reg |= UCR1_TXMPTYEN;
					writel(reg, dev->base + UCR1);
				}
				break;
			}
			reg = readl(dev->base + UMCR);
			if (parity)
				reg |= UMCR_TXB8;
			else
				reg &= ~UMCR_TXB8;
			writel(reg, dev->base + UMCR);
			dev->last_parity = parity;
		} /* parity change */

		writel(dev->txbuf.buf[dev->txbuf.tail], dev->base + URTX0);
		dev->txbuf.tail = CIRC_NEXT(dev->txbuf.tail, dev->txbufsize);
		if (CIRC_SPACE(dev->txbuf.head,
			       dev->txbuf.tail,
			       dev->txbufsize) >= dev->maxtxmsg)
			wake_up(&dev->queue);
	}
	if (!CIRC_CNT(dev->txbuf.head,
		      dev->txbuf.tail,
		      dev->txbufsize)) {
		reg = readl(dev->base + UCR1);
		if (reg & (UCR1_TRDYEN | UCR1_TXMPTYEN)) {
			reg &= ~(UCR1_TRDYEN | UCR1_TXMPTYEN);
			writel(reg, dev->base + UCR1);
		}
	}
}

static irqreturn_t irq_handler(int irq, void *dev_id)
{
	struct sas_dev *dev = dev_id;
	unsigned int sts;

	sts = readl(dev->base + USR1);
	if (sts & USR1_RRDY)
		sas_rxint(dev_id);

	if (sts & USR1_TRDY)
		sas_txint(dev_id);

	sts = readl(dev->base + USR2);
	if (sts & USR2_ORE)
		writel(USR2_ORE, dev->base + USR2);
	return IRQ_HANDLED;
}

/*
 * We don't want address detection, so force an
 * address match by entering loopback and transmitting
 * a character with the mark bit set.
 */
static void force_address_match(struct sas_dev *dev)
{
	writel(UCR1_UARTEN, dev->base + UCR1);

	/* loopback */
	writel(UTS_LOOP, dev->base + IMX21_UTS);

	/* mark parity */
	writel(UMCR_TXB8 | UMCR_MDEN, dev->base + UMCR);

	/* transmit a null */
	writel(0, dev->base + URTX0);

	/* wait for receiver ready */
	usleep_range(1000, 2000);
	(void)readl(dev->base + URXD0);

	/* out of loopback */
	writel(0, dev->base + IMX21_UTS);

	/* and space parity */
	writel(UMCR_MDEN, dev->base + UMCR);
}

static int sas_open(struct inode *inode, struct file *file)
{
	int rval = 0;
	unsigned long flags;
	struct sas_dev *dev = container_of(inode->i_cdev,
					   struct sas_dev, cdev);
	file->private_data = dev;
	spin_lock_irqsave(&dev->lock, flags);
	if (1 == ++dev->open_count) {
		rval = clk_enable(dev->clk_per);
		if (rval)
			goto out;
		rval = clk_enable(dev->clk_ipg);
		if (rval) {
			clk_disable(dev->clk_per);
			goto out;
		}
		rval = devm_request_irq(&dev->pdev->dev,
					  dev->irq, irq_handler,
					  IRQF_TRIGGER_PROBE, DRIVER_NAME, dev);
		if (!rval) {
			writel(0x0, dev->base + UCR1);
			writel(0x0, dev->base + UCR2);

			while (!(readl(dev->base + UCR2) & UCR2_SRST))
				;

			writel(IMX21_UCR3_RXDMUXSEL | UCR3_ADNIMP,
			       dev->base + UCR3);
			writel(0x8000, dev->base + UCR4);
			writel(0x002b, dev->base + UESC);
			writel(0x0, dev->base + UTIM);
			writel(0x0, dev->base + IMX1_UTS);
			/* divide input clock by 2, receive fifo 1, txtl 2 */
			writel((4 << 7) | 0x801, dev->base + UFCR);
			writel(0xf, dev->base + UBIR);
			writel(clk_get_rate(dev->clk_per) / (2 * dev->baud),
			       dev->base + UBMR);
			writel(UMCR_MDEN, dev->base + UMCR);
			writel(UCR2_WS | UCR2_IRTS
			       | UCR2_RXEN | UCR2_TXEN
			       | UCR2_SRST | UCR2_PREN,
			       dev->base + UCR2);

			force_address_match(dev);
			writel(UCR1_UARTEN | UCR1_RRDYEN, dev->base + UCR1);

			dev->rxbuf.head =
			dev->rxbuf.tail =
			dev->txbuf.head =
			dev->txbuf.tail = 0;
			dev->rxmsgadd =
			dev->rxmsgtake = 0;
			dev->rxmsgs[0].start = 0;
			dev->last_parity = 0xff;
		} else {
			dev_err(&dev->pdev->dev,
				"Error %d requesting irq %d\n",
				rval, dev->irq);
			--dev->open_count;
		}
	}
out:
	spin_unlock_irqrestore(&dev->lock, flags);
	return rval;
}

static int sas_release(struct inode *inode, struct file *file)
{
	unsigned long flags;
	struct sas_dev *dev = container_of(inode->i_cdev,
					   struct sas_dev, cdev);
	spin_lock_irqsave(&dev->lock, flags);
	if (0 == --dev->open_count) {
		writel(0x0, dev->base + UCR1);
		writel(0x0, dev->base + UCR2);
		devm_free_irq(&dev->pdev->dev, dev->irq, dev);
		clk_disable(dev->clk_ipg);
		clk_disable(dev->clk_per);
	}

	spin_unlock_irqrestore(&dev->lock, flags);
	return -1;
}

static struct file_operations const sas_fops = {
	.owner = THIS_MODULE,
	.read = sas_read,
	.write = sas_write,
	.poll = sas_poll,
	.open = sas_open,
	.release = sas_release,
};

static ssize_t show_ibdelay(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct sas_dev *sas = dev_get_drvdata(dev);
	return sprintf(buf, "%d\n", sas ? sas->interbyte_delay : -1);
}

static ssize_t store_ibdelay(struct device *dev,
			     struct device_attribute *attr,
			     const char *buf,
			     size_t count)
{
	u32 val;
	struct sas_dev *sas = dev_get_drvdata(dev);
	if (1 == sscanf(buf, "%u", &val)) {
		if (sas) {
			sas->interbyte_delay = val;
			return count;
		} else {
			return -ENODEV;
		}
	} else {
		return -EINVAL;
	}
}

static struct kobj_attribute ibdelay =
__ATTR(ibdelay, 0644, (void *)show_ibdelay, (void *)store_ibdelay);

static ssize_t show_rxhead(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct sas_dev *sas = dev_get_drvdata(dev);
	return sprintf(buf, "%d\n", sas ? sas->rxbuf.head : -1);
}

static struct kobj_attribute rxhead =
__ATTR(rxhead, 0644, (void *)show_rxhead, NULL);

static ssize_t show_rxtail(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct sas_dev *sas = dev_get_drvdata(dev);
	return sprintf(buf, "%d\n", sas ? sas->rxbuf.tail : -1);
}

static struct kobj_attribute rxtail =
__ATTR(rxtail, 0644, (void *)show_rxtail, NULL);

static ssize_t show_txhead(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct sas_dev *sas = dev_get_drvdata(dev);
	return sprintf(buf, "%d\n", sas ? sas->txbuf.head : -1);
}

static struct kobj_attribute txhead =
__ATTR(txhead, 0644, (void *)show_txhead, NULL);

static ssize_t show_txtail(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct sas_dev *sas = dev_get_drvdata(dev);
	return sprintf(buf, "%d\n", sas ? sas->txbuf.tail : -1);
}

static struct kobj_attribute txtail =
__ATTR(txtail, 0644, (void *)show_txtail, NULL);

static ssize_t show_rxmsgadd(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct sas_dev *sas = dev_get_drvdata(dev);
	return sprintf(buf, "%d: [%d:%d]\n",
		sas ? sas->rxmsgadd : -1,
		sas ? sas->rxmsgs[sas->rxmsgadd].start : -1,
		sas ? sas->rxmsgs[sas->rxmsgadd].end : -1);
}

static struct kobj_attribute rxmsgadd =
__ATTR(rxmsgadd, 0644, (void *)show_rxmsgadd, NULL);

static ssize_t show_rxmsgtake(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct sas_dev *sas = dev_get_drvdata(dev);
	return sprintf(buf, "%d: [%d:%d]\n",
		sas ? sas->rxmsgtake : -1,
		sas ? sas->rxmsgs[sas->rxmsgtake].start : -1,
		sas ? sas->rxmsgs[sas->rxmsgtake].end : -1);
}

static struct kobj_attribute rxmsgtake =
__ATTR(rxmsgtake, 0644, (void *)show_rxmsgtake, NULL);

static struct attribute *sas_attrs[] = {
	&ibdelay.attr,
	&rxhead.attr,
	&rxtail.attr,
	&txhead.attr,
	&txtail.attr,
	&rxmsgadd.attr,
	&rxmsgtake.attr,
	NULL,
};

static struct attribute_group sas_attr_grp = {
	.attrs = sas_attrs,
};

static int sas_of_probe(struct platform_device *pdev,
			struct device_node *np,
			struct sas_dev *dev)
{
	dev->irq = irq_of_parse_and_map(np, 0);
	if (of_property_read_u32(np, "baud", &dev->baud))
		return -EINVAL;
	if (of_property_read_u32(np, "interbyte_delay", &dev->interbyte_delay))
		return -EINVAL;
	if (of_property_read_u32(np, "rxbufsize", &dev->rxbufsize))
		return -EINVAL;
	dev->maxrxmsgs = dev->rxbufsize / 4;
	if ((dev->maxrxmsgs == 0) ||
	    (dev->maxrxmsgs & (dev->maxrxmsgs-1))) {
		dev_err(&pdev->dev,
			"maxrxmsgs %u must be a non-zero power of 2\n",
			dev->maxrxmsgs);
	}
	if (of_property_read_u32(np, "txbufsize", &dev->txbufsize))
		return -EINVAL;
	if (of_property_read_u32(np, "maxtxmsg", &dev->maxtxmsg))
		return -EINVAL;
	if (of_property_read_u32(np, "flush_on_mark", &dev->flush_on_mark))
		return -EINVAL;
	/* force power of two for transmit and receive buffers */
	if ((dev->rxbufsize & (dev->rxbufsize-1)) ||
	    (dev->rxbufsize == 0) ||
	    (dev->txbufsize & (dev->txbufsize-1)) ||
	    (dev->txbufsize == 0)) {
		dev_err(&pdev->dev,
			"rx/txbufsize must be a non-zero power of 2\n");
		return -EINVAL;
	}
	return 0;
}

static int sas_probe(struct platform_device *pdev)
{
	int result = 0;
	struct sas_dev *dev;
	struct device_node *np = pdev->dev.of_node;
	void __iomem *base;
	struct resource *res;

	if (!np)
		return -ENODEV;

	dev = devm_kzalloc(&pdev->dev, sizeof(struct sas_dev), GFP_KERNEL);
	if (!dev) {
		dev_err(&pdev->dev, "%s: alloc failure", DRIVER_NAME);
		result = -ENOMEM;
		goto fail_entry;
	}

	dev->pdev = pdev;
	cdev_init(&dev->cdev, &sas_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &sas_fops;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "%s: IORESOURCE_MEM", DRIVER_NAME);
		result = -ENODEV;
		goto fail_entry;
	}

	base = devm_ioremap(&pdev->dev, res->start, PAGE_SIZE);
	if (!base) {
		dev_err(&pdev->dev, "%s: ioremap", DRIVER_NAME);
		result = -ENOMEM;
		goto fail_entry;
	}
	dev->base = base;

	dev->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
	if (IS_ERR(dev->clk_ipg)) {
		result = PTR_ERR(dev->clk_ipg);
		dev_err(&pdev->dev, "failed to get ipg clk: %d\n", result);
		return result;
	}

	dev->clk_per = devm_clk_get(&pdev->dev, "per");
	if (IS_ERR(dev->clk_per)) {
		result = PTR_ERR(dev->clk_per);
		dev_err(&pdev->dev, "failed to get per clk: %d\n", result);
		return result;
	}

	if (0 != sas_of_probe(pdev, np, dev)) {
		dev_err(&pdev->dev, "Invalid dt spec\n");
		result = -ENODEV;
		goto fail_entry;
	}

	dev->rxbuf.buf = devm_kzalloc(&pdev->dev, dev->rxbufsize, GFP_KERNEL);
	if (!dev->rxbuf.buf) {
		dev_err(&pdev->dev, "%s: allocating rxbuf(%d)",
			DRIVER_NAME, dev->rxbufsize);
		goto fail_entry;
	}

	dev->rxmsgs = devm_kzalloc(&pdev->dev,
				   dev->maxrxmsgs * sizeof(dev->rxmsgs[0]),
				   GFP_KERNEL);
	if (!dev->rxmsgs) {
		dev_err(&pdev->dev, "%s: allocating rxmsgs(%d)",
			DRIVER_NAME, dev->maxrxmsgs);
		goto fail_entry;
	}

	dev->txbuf.buf = devm_kzalloc(&pdev->dev, dev->txbufsize, GFP_KERNEL);
	if (!dev->txbuf.buf) {
		dev_err(&pdev->dev, "%s: allocating txbuf(%d)",
			DRIVER_NAME, dev->txbufsize);
		goto fail_entry;
	}

	dev->txpbuf = devm_kzalloc(&pdev->dev,
				   (dev->txbufsize+7)/8,
				   GFP_KERNEL);
	if (!dev->txpbuf) {
		dev_err(&pdev->dev, "%s: allocating txbuf(%d)", DRIVER_NAME,
			(dev->txbufsize+7)/8);
		goto fail_entry;
	}

	mutex_init(&dev->rx_lock);
	mutex_init(&dev->tx_lock);

	init_waitqueue_head(&dev->queue);

	hrtimer_init(&dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	dev->timer.function = rx_timer;

	result = cdev_add(&dev->cdev, devnum, 1);
	if (result < 0) {
		dev_err(&pdev->dev, "%s: couldn't add device: err %d\n",
			DRIVER_NAME, result);
		goto fail_cdevadd;
	}

	result = sysfs_create_group(&pdev->dev.kobj, &sas_attr_grp);
	if (result) {
		pr_err("failed to create sysfs entries");
		goto fail_cdevadd;
	}

	result = clk_prepare_enable(dev->clk_per);
	if (result)
		goto fail_cdevadd;
	result = clk_prepare_enable(dev->clk_ipg);
	if (result) {
		clk_disable_unprepare(dev->clk_per);
		goto fail_cdevadd;
	}

	spin_lock_init(&dev->lock);

	dev->chrdev = device_create(sas_class, &platform_bus,
				    devnum, NULL, "%s", DRIVER_NAME);
	platform_set_drvdata(pdev, dev);

	dev_dbg(&pdev->dev, "%s: uart_clk == %ld\n",
		DRIVER_NAME, clk_get_rate(dev->clk_per));
	dev_dbg(&pdev->dev, "%s: ipg_clk == %ld\n",
		DRIVER_NAME, clk_get_rate(dev->clk_per));
	dev_dbg(&pdev->dev, "%s: irq == %d\n",
		DRIVER_NAME, dev->irq);
	dev_dbg(&pdev->dev, "%s: rxirq == %d\n",
		DRIVER_NAME, dev->rxirq);
	dev_dbg(&pdev->dev, "%s: txirq == %d\n",
		DRIVER_NAME, dev->txirq);
	dev_dbg(&pdev->dev, "%s: baud  == %u\n",
		DRIVER_NAME, dev->baud);
	dev_dbg(&pdev->dev, "%s: ib_delay == %u ms\n",
		DRIVER_NAME, dev->interbyte_delay);
	dev_dbg(&pdev->dev, "%s: clks == %p/%p\n",
		DRIVER_NAME, dev->clk_ipg, dev->clk_per);
	dev_dbg(&pdev->dev, "%s: mem == %p\n",
		DRIVER_NAME, (void *)res->start);
	dev_dbg(&pdev->dev, "%s: rxbufsize == %u\n",
		DRIVER_NAME, dev->rxbufsize);
	dev_dbg(&pdev->dev, "%s: maxrxmsgs == %u\n",
		DRIVER_NAME, dev->maxrxmsgs);
	dev_dbg(&pdev->dev, "%s: txbufsize == %u\n",
		DRIVER_NAME, dev->txbufsize);
	dev_dbg(&pdev->dev, "%s: maxtxmsg == %u\n",
		DRIVER_NAME, dev->maxtxmsg);
	dev_dbg(&pdev->dev, "%s: sas_dev == %p\n",
		DRIVER_NAME, dev);
	dev_dbg(&pdev->dev, "%s: flush_on_mark == %d\n",
		DRIVER_NAME, dev->flush_on_mark);
	dev_info(&pdev->dev, "sas driver installed\n");
	return 0;

fail_cdevadd:
	devm_kfree(&pdev->dev, dev);
fail_entry:
	return result;
}

static int sas_remove(struct platform_device *pdev)
{
	struct sas_dev *dev = platform_get_drvdata(pdev);
	if (dev) {
		sysfs_remove_group(&pdev->dev.kobj, &sas_attr_grp);
		clk_disable_unprepare(dev->clk_per);
		clk_disable_unprepare(dev->clk_ipg);
		cdev_del(&dev->cdev);
	}

	dev_info(&pdev->dev, "sas driver released\n");
	return 0;
}

static const struct of_device_id sas_ids[] = {
	{ .compatible = "boundary,sas", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sas_ids);

static struct platform_driver sas_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner	= THIS_MODULE,
		.of_match_table = sas_ids,
		},
	.probe = sas_probe,
	.remove = sas_remove,
};

static char *sas_devnode(struct device *dev, umode_t *mode)
{
	if (mode)
		*mode = S_IRUGO | S_IWUSR;
	return kasprintf(GFP_KERNEL, "%s", DRIVER_NAME);
}

static int __init sas_init(void)
{
	int result;

	/* Create a sysfs class. */
	sas_class = class_create(THIS_MODULE, "sas");
	if (IS_ERR(sas_class)) {
		result = PTR_ERR(sas_class);
		goto fail_class;
	}
	sas_class->devnode = sas_devnode;

	result = alloc_chrdev_region(&devnum, 0, 1, DRIVER_NAME);
	if (result < 0) {
		pr_err("%s: couldn't chrdevs: err %d\n",
		       DRIVER_NAME, result);
		goto fail_chrdev;
	}
	sas_major = MAJOR(devnum);

	result = platform_driver_register(&sas_driver);
	if (result < 0) {
		pr_err("%s: couldn't register driver, err %d\n",
		       DRIVER_NAME, result);
		goto fail_platform;
	}

	return 0;

fail_platform:
	unregister_chrdev_region(devnum, 1);

fail_chrdev:
	class_destroy(sas_class);
	sas_class = NULL;

fail_class:
	return result;
}

static void __exit sas_cleanup(void)
{
	platform_driver_unregister(&sas_driver);

	unregister_chrdev_region(devnum, 1);

	if (!IS_ERR(sas_class))
		class_destroy(sas_class);
}

module_init(sas_init);
module_exit(sas_cleanup);
MODULE_LICENSE("GPL");