kernel: Patch in i2c, sbefifo, and occ drivers

Change-Id: I845752ca9f64f42f1fc46e14cac90a48f7195ca3
Signed-off-by: Edward A. James <eajames@us.ibm.com>
diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-drivers-fsi-Add-FSI-SBEFIFO-driver.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-drivers-fsi-Add-FSI-SBEFIFO-driver.patch
new file mode 100644
index 0000000..b29e0e9
--- /dev/null
+++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-drivers-fsi-Add-FSI-SBEFIFO-driver.patch
@@ -0,0 +1,899 @@
+From patchwork Thu May 11 02:52:53 2017
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Subject: [linux,dev-4.10] drivers: fsi: Add FSI SBEFIFO driver
+From: eajames@linux.vnet.ibm.com
+X-Patchwork-Id: 760920
+Message-Id: <1494471173-6077-1-git-send-email-eajames@linux.vnet.ibm.com>
+To: openbmc@lists.ozlabs.org
+Cc: "Edward A. James" <eajames@us.ibm.com>, bradleyb@fuzziesquirrel.com
+Date: Wed, 10 May 2017 21:52:53 -0500
+
+From: "Edward A. James" <eajames@us.ibm.com>
+
+IBM POWER9 processors contain some embedded hardware and software bits
+collectively referred to as the self boot engine (SBE).  One role of
+the SBE is to act as a proxy that provides access to the registers of
+the POWER chip from other (embedded) systems.
+
+The POWER9 chip contains a hardware frontend for communicating with
+the SBE from remote systems called the SBEFIFO.  The SBEFIFO logic
+is contained within an FSI CFAM (see Documentation/fsi) and as such
+the driver implements an FSI bus device.
+
+The SBE expects to communicate using a defined wire protocol; however,
+the driver knows nothing of the protocol and only provides raw access
+to the fifo device to userspace applications wishing to communicate with
+the SBE using the wire protocol.
+
+The SBEFIFO consists of two hardware fifos.  The upstream fifo is used
+by the driver to transfer data to the SBE on the POWER chip, from the
+system hosting the driver.  The downstream fifo is used by the driver to
+transfer data from the SBE on the power chip to the system hosting the
+driver.
+
+Signed-off-by: Edward A. James <eajames@us.ibm.com>
+Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
+---
+ drivers/fsi/Kconfig       |   5 +
+ drivers/fsi/Makefile      |   1 +
+ drivers/fsi/fsi-sbefifo.c | 824 ++++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 830 insertions(+)
+ create mode 100644 drivers/fsi/fsi-sbefifo.c
+
+diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
+index fc031ac..39527fa 100644
+--- a/drivers/fsi/Kconfig
++++ b/drivers/fsi/Kconfig
+@@ -31,6 +31,11 @@ config FSI_SCOM
+ 	---help---
+ 	This option enables an FSI based SCOM device driver.
+ 
++config FSI_SBEFIFO
++	tristate "SBEFIFO FSI client device driver"
++	---help---
++	This option enables an FSI based SBEFIFO device driver.
++
+ endif
+ 
+ endmenu
+diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
+index 65eb99d..851182e 100644
+--- a/drivers/fsi/Makefile
++++ b/drivers/fsi/Makefile
+@@ -3,3 +3,4 @@ obj-$(CONFIG_FSI) += fsi-core.o
+ obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o
+ obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
+ obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
++obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o
+diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c
+new file mode 100644
+index 0000000..b49aec2
+--- /dev/null
++++ b/drivers/fsi/fsi-sbefifo.c
+@@ -0,0 +1,824 @@
++/*
++ * Copyright (C) IBM Corporation 2017
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/delay.h>
++#include <linux/errno.h>
++#include <linux/idr.h>
++#include <linux/fsi.h>
++#include <linux/list.h>
++#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/poll.h>
++#include <linux/sched.h>
++#include <linux/slab.h>
++#include <linux/timer.h>
++#include <linux/uaccess.h>
++
++/*
++ * The SBEFIFO is a pipe-like FSI device for communicating with
++ * the self boot engine on POWER processors.
++ */
++
++#define DEVICE_NAME		"sbefifo"
++#define FSI_ENGID_SBE		0x22
++#define SBEFIFO_BUF_CNT		32
++
++#define SBEFIFO_UP		0x00	/* Up register offset */
++#define SBEFIFO_DWN		0x40	/* Down register offset */
++
++#define SBEFIFO_STS		0x04
++#define   SBEFIFO_EMPTY			BIT(20)
++#define SBEFIFO_EOT_RAISE	0x08
++#define   SBEFIFO_EOT_MAGIC		0xffffffff
++#define SBEFIFO_EOT_ACK		0x14
++
++struct sbefifo {
++	struct timer_list poll_timer;
++	struct fsi_device *fsi_dev;
++	struct miscdevice mdev;
++	wait_queue_head_t wait;
++	struct list_head link;
++	struct list_head xfrs;
++	struct kref kref;
++	spinlock_t lock;
++	char name[32];
++	int idx;
++	int rc;
++};
++
++struct sbefifo_buf {
++	u32 buf[SBEFIFO_BUF_CNT];
++	unsigned long flags;
++#define SBEFIFO_BUF_FULL		1
++	u32 *rpos;
++	u32 *wpos;
++};
++
++struct sbefifo_xfr {
++	struct sbefifo_buf *rbuf;
++	struct sbefifo_buf *wbuf;
++	struct list_head client;
++	struct list_head xfrs;
++	unsigned long flags;
++#define SBEFIFO_XFR_WRITE_DONE		1
++#define SBEFIFO_XFR_RESP_PENDING	2
++#define SBEFIFO_XFR_COMPLETE		3
++#define SBEFIFO_XFR_CANCEL		4
++};
++
++struct sbefifo_client {
++	struct sbefifo_buf rbuf;
++	struct sbefifo_buf wbuf;
++	struct list_head xfrs;
++	struct sbefifo *dev;
++	struct kref kref;
++};
++
++static struct list_head sbefifo_fifos;
++
++static DEFINE_IDA(sbefifo_ida);
++
++static int sbefifo_inw(struct sbefifo *sbefifo, int reg, u32 *word)
++{
++	int rc;
++	u32 raw_word;
++
++	rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word,
++			     sizeof(raw_word));
++	if (rc)
++		return rc;
++
++	*word = be32_to_cpu(raw_word);
++	return 0;
++}
++
++static int sbefifo_outw(struct sbefifo *sbefifo, int reg, u32 word)
++{
++	u32 raw_word = cpu_to_be32(word);
++
++	return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word,
++				sizeof(raw_word));
++}
++
++static int sbefifo_readw(struct sbefifo *sbefifo, u32 *word)
++{
++	return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DWN, word,
++			       sizeof(*word));
++}
++
++static int sbefifo_writew(struct sbefifo *sbefifo, u32 word)
++{
++	return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word,
++				sizeof(word));
++}
++
++static int sbefifo_ack_eot(struct sbefifo *sbefifo)
++{
++	u32 discard;
++	int ret;
++
++	 /* Discard the EOT word. */
++	ret = sbefifo_readw(sbefifo, &discard);
++	if (ret)
++		return ret;
++
++	return sbefifo_outw(sbefifo, SBEFIFO_DWN | SBEFIFO_EOT_ACK,
++			SBEFIFO_EOT_MAGIC);
++}
++
++static size_t sbefifo_dev_nwreadable(u32 sts)
++{
++	static const u32 FIFO_NTRY_CNT_MSK = 0x000f0000;
++	static const unsigned int FIFO_NTRY_CNT_SHIFT = 16;
++
++	return (sts & FIFO_NTRY_CNT_MSK) >> FIFO_NTRY_CNT_SHIFT;
++}
++
++static size_t sbefifo_dev_nwwriteable(u32 sts)
++{
++	static const size_t FIFO_DEPTH = 8;
++
++	return FIFO_DEPTH - sbefifo_dev_nwreadable(sts);
++}
++
++static void sbefifo_buf_init(struct sbefifo_buf *buf)
++{
++	WRITE_ONCE(buf->rpos, buf->buf);
++	WRITE_ONCE(buf->wpos, buf->buf);
++}
++
++static size_t sbefifo_buf_nbreadable(struct sbefifo_buf *buf)
++{
++	size_t n;
++	u32 *rpos = READ_ONCE(buf->rpos);
++	u32 *wpos = READ_ONCE(buf->wpos);
++
++	if (test_bit(SBEFIFO_BUF_FULL, &buf->flags))
++		n = SBEFIFO_BUF_CNT;
++	else if (rpos <= wpos)
++		n = wpos - rpos;
++	else
++		n = (buf->buf + SBEFIFO_BUF_CNT) - rpos;
++
++	return n << 2;
++}
++
++static size_t sbefifo_buf_nbwriteable(struct sbefifo_buf *buf)
++{
++	size_t n;
++	u32 *rpos = READ_ONCE(buf->rpos);
++	u32 *wpos = READ_ONCE(buf->wpos);
++
++	if (test_bit(SBEFIFO_BUF_FULL, &buf->flags))
++		n = 0;
++	else if (wpos < rpos)
++		n = rpos - wpos;
++	else
++		n = (buf->buf + SBEFIFO_BUF_CNT) - wpos;
++
++	return n << 2;
++}
++
++/*
++ * Update pointers and flags after doing a buffer read.  Return true if the
++ * buffer is now empty;
++ */
++static bool sbefifo_buf_readnb(struct sbefifo_buf *buf, size_t n)
++{
++	u32 *rpos = READ_ONCE(buf->rpos);
++	u32 *wpos = READ_ONCE(buf->wpos);
++
++	if (n)
++		clear_bit(SBEFIFO_BUF_FULL, &buf->flags);
++
++	rpos += (n >> 2);
++	if (rpos == buf->buf + SBEFIFO_BUF_CNT)
++		rpos = buf->buf;
++
++	WRITE_ONCE(buf->rpos, rpos);
++	return rpos == wpos;
++}
++
++/*
++ * Update pointers and flags after doing a buffer write.  Return true if the
++ * buffer is now full.
++ */
++static bool sbefifo_buf_wrotenb(struct sbefifo_buf *buf, size_t n)
++{
++	u32 *rpos = READ_ONCE(buf->rpos);
++	u32 *wpos = READ_ONCE(buf->wpos);
++
++	wpos += (n >> 2);
++	if (wpos == buf->buf + SBEFIFO_BUF_CNT)
++		wpos = buf->buf;
++	if (wpos == rpos)
++		set_bit(SBEFIFO_BUF_FULL, &buf->flags);
++
++	WRITE_ONCE(buf->wpos, wpos);
++	return rpos == wpos;
++}
++
++static void sbefifo_free(struct kref *kref)
++{
++	struct sbefifo *sbefifo;
++
++	sbefifo = container_of(kref, struct sbefifo, kref);
++	kfree(sbefifo);
++}
++
++static void sbefifo_get(struct sbefifo *sbefifo)
++{
++	kref_get(&sbefifo->kref);
++}
++
++static void sbefifo_put(struct sbefifo *sbefifo)
++{
++	kref_put(&sbefifo->kref, sbefifo_free);
++}
++
++static struct sbefifo_xfr *sbefifo_enq_xfr(struct sbefifo_client *client)
++{
++	struct sbefifo *sbefifo = client->dev;
++	struct sbefifo_xfr *xfr;
++
++	xfr = kzalloc(sizeof(*xfr), GFP_KERNEL);
++	if (!xfr)
++		return NULL;
++
++	xfr->rbuf = &client->rbuf;
++	xfr->wbuf = &client->wbuf;
++	list_add_tail(&xfr->xfrs, &sbefifo->xfrs);
++	list_add_tail(&xfr->client, &client->xfrs);
++
++	return xfr;
++}
++
++static struct sbefifo_xfr *sbefifo_client_next_xfr(
++		struct sbefifo_client *client)
++{
++	if (list_empty(&client->xfrs))
++		return NULL;
++
++	return container_of(client->xfrs.next, struct sbefifo_xfr,
++			client);
++}
++
++static bool sbefifo_xfr_rsp_pending(struct sbefifo_client *client)
++{
++	struct sbefifo_xfr *xfr;
++
++	xfr = sbefifo_client_next_xfr(client);
++	if (xfr && test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags))
++		return true;
++
++	return false;
++}
++
++static struct sbefifo_client *sbefifo_new_client(struct sbefifo *sbefifo)
++{
++	struct sbefifo_client *client;
++
++	client = kzalloc(sizeof(*client), GFP_KERNEL);
++	if (!client)
++		return NULL;
++
++	kref_init(&client->kref);
++	client->dev = sbefifo;
++	sbefifo_buf_init(&client->rbuf);
++	sbefifo_buf_init(&client->wbuf);
++	INIT_LIST_HEAD(&client->xfrs);
++
++	sbefifo_get(sbefifo);
++
++	return client;
++}
++
++static void sbefifo_client_release(struct kref *kref)
++{
++	struct sbefifo_client *client;
++	struct sbefifo_xfr *xfr;
++
++	client = container_of(kref, struct sbefifo_client, kref);
++	list_for_each_entry(xfr, &client->xfrs, client) {
++		/*
++		 * The client left with pending or running xfrs.
++		 * Cancel them.
++		 */
++		set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
++		sbefifo_get(client->dev);
++		if (mod_timer(&client->dev->poll_timer, jiffies))
++			sbefifo_put(client->dev);
++	}
++
++	sbefifo_put(client->dev);
++	kfree(client);
++}
++
++static void sbefifo_get_client(struct sbefifo_client *client)
++{
++	kref_get(&client->kref);
++}
++
++static void sbefifo_put_client(struct sbefifo_client *client)
++{
++	kref_put(&client->kref, sbefifo_client_release);
++}
++
++static struct sbefifo_xfr *sbefifo_next_xfr(struct sbefifo *sbefifo)
++{
++	struct sbefifo_xfr *xfr, *tmp;
++
++	list_for_each_entry_safe(xfr, tmp, &sbefifo->xfrs, xfrs) {
++		if (unlikely(test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags))) {
++			/* Discard cancelled transfers. */
++			list_del(&xfr->xfrs);
++			kfree(xfr);
++			continue;
++		}
++		return xfr;
++	}
++
++	return NULL;
++}
++
++static void sbefifo_poll_timer(unsigned long data)
++{
++	static const unsigned long EOT_MASK = 0x000000ff;
++	struct sbefifo *sbefifo = (void *)data;
++	struct sbefifo_buf *rbuf, *wbuf;
++	struct sbefifo_xfr *xfr = NULL;
++	struct sbefifo_buf drain;
++	size_t devn, bufn;
++	int eot = 0;
++	int ret = 0;
++	u32 sts;
++	int i;
++
++	spin_lock(&sbefifo->lock);
++	xfr = list_first_entry_or_null(&sbefifo->xfrs, struct sbefifo_xfr,
++			xfrs);
++	if (!xfr)
++		goto out_unlock;
++
++	rbuf = xfr->rbuf;
++	wbuf = xfr->wbuf;
++
++	if (unlikely(test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags))) {
++		/* The client left. */
++		rbuf = &drain;
++		wbuf = &drain;
++		sbefifo_buf_init(&drain);
++		if (!test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags))
++			set_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags);
++	}
++
++	 /* Drain the write buffer. */
++	while ((bufn = sbefifo_buf_nbreadable(wbuf))) {
++		ret = sbefifo_inw(sbefifo, SBEFIFO_UP | SBEFIFO_STS,
++				&sts);
++		if (ret)
++			goto out;
++
++		devn = sbefifo_dev_nwwriteable(sts);
++		if (devn == 0) {
++			/* No open slot for write.  Reschedule. */
++			sbefifo->poll_timer.expires = jiffies +
++				msecs_to_jiffies(500);
++			add_timer(&sbefifo->poll_timer);
++			goto out_unlock;
++		}
++
++		devn = min_t(size_t, devn, bufn >> 2);
++		for (i = 0; i < devn; i++) {
++			ret = sbefifo_writew(sbefifo, *wbuf->rpos);
++			if (ret)
++				goto out;
++
++			sbefifo_buf_readnb(wbuf, 1 << 2);
++		}
++	}
++
++	 /* Send EOT if the writer is finished. */
++	if (test_and_clear_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags)) {
++		ret = sbefifo_outw(sbefifo,
++				SBEFIFO_UP | SBEFIFO_EOT_RAISE,
++				SBEFIFO_EOT_MAGIC);
++		if (ret)
++			goto out;
++
++		/* Inform reschedules that the writer is finished. */
++		set_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags);
++	}
++
++	/* Nothing left to do if the writer is not finished. */
++	if (!test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags))
++		goto out;
++
++	 /* Fill the read buffer. */
++	while ((bufn = sbefifo_buf_nbwriteable(rbuf))) {
++		ret = sbefifo_inw(sbefifo, SBEFIFO_DWN | SBEFIFO_STS, &sts);
++		if (ret)
++			goto out;
++
++		devn = sbefifo_dev_nwreadable(sts);
++		if (devn == 0) {
++			/* No data yet.  Reschedule. */
++			sbefifo->poll_timer.expires = jiffies +
++				msecs_to_jiffies(500);
++			add_timer(&sbefifo->poll_timer);
++			goto out_unlock;
++		}
++
++		/* Fill.  The EOT word is discarded.  */
++		devn = min_t(size_t, devn, bufn >> 2);
++		eot = (sts & EOT_MASK) != 0;
++		if (eot)
++			devn--;
++
++		for (i = 0; i < devn; i++) {
++			ret = sbefifo_readw(sbefifo, rbuf->wpos);
++			if (ret)
++				goto out;
++
++			if (likely(!test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags)))
++				sbefifo_buf_wrotenb(rbuf, 1 << 2);
++		}
++
++		if (eot) {
++			ret = sbefifo_ack_eot(sbefifo);
++			if (ret)
++				goto out;
++
++			set_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags);
++			list_del(&xfr->xfrs);
++			if (unlikely(test_bit(SBEFIFO_XFR_CANCEL,
++							&xfr->flags)))
++				kfree(xfr);
++			break;
++		}
++	}
++
++out:
++	if (unlikely(ret)) {
++		sbefifo->rc = ret;
++		dev_err(&sbefifo->fsi_dev->dev,
++				"Fatal bus access failure: %d\n", ret);
++		list_for_each_entry(xfr, &sbefifo->xfrs, xfrs)
++			kfree(xfr);
++		INIT_LIST_HEAD(&sbefifo->xfrs);
++
++	} else if (eot && sbefifo_next_xfr(sbefifo)) {
++		sbefifo_get(sbefifo);
++		sbefifo->poll_timer.expires = jiffies;
++		add_timer(&sbefifo->poll_timer);
++	}
++
++	sbefifo_put(sbefifo);
++	wake_up(&sbefifo->wait);
++
++out_unlock:
++	spin_unlock(&sbefifo->lock);
++}
++
++static int sbefifo_open(struct inode *inode, struct file *file)
++{
++	struct sbefifo *sbefifo = container_of(file->private_data,
++			struct sbefifo, mdev);
++	struct sbefifo_client *client;
++	int ret;
++
++	ret = READ_ONCE(sbefifo->rc);
++	if (ret)
++		return ret;
++
++	client = sbefifo_new_client(sbefifo);
++	if (!client)
++		return -ENOMEM;
++
++	file->private_data = client;
++
++	return 0;
++}
++
++static unsigned int sbefifo_poll(struct file *file, poll_table *wait)
++{
++	struct sbefifo_client *client = file->private_data;
++	struct sbefifo *sbefifo = client->dev;
++	unsigned int mask = 0;
++
++	poll_wait(file, &sbefifo->wait, wait);
++
++	if (READ_ONCE(sbefifo->rc))
++		mask |= POLLERR;
++
++	if (sbefifo_buf_nbreadable(&client->rbuf))
++		mask |= POLLIN;
++
++	if (sbefifo_buf_nbwriteable(&client->wbuf))
++		mask |= POLLOUT;
++
++	return mask;
++}
++
++static ssize_t sbefifo_read(struct file *file, char __user *buf,
++		size_t len, loff_t *offset)
++{
++	struct sbefifo_client *client = file->private_data;
++	struct sbefifo *sbefifo = client->dev;
++	struct sbefifo_xfr *xfr;
++	ssize_t ret = 0;
++	size_t n;
++
++	WARN_ON(*offset);
++
++	if (!access_ok(VERIFY_WRITE, buf, len))
++		return -EFAULT;
++
++	if ((len >> 2) << 2 != len)
++		return -EINVAL;
++
++	if ((file->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client))
++		return -EAGAIN;
++
++	sbefifo_get_client(client);
++	if (wait_event_interruptible(sbefifo->wait,
++				(ret = READ_ONCE(sbefifo->rc)) ||
++				(n = sbefifo_buf_nbreadable(
++						&client->rbuf)))) {
++		sbefifo_put_client(client);
++		return -ERESTARTSYS;
++	}
++	if (ret) {
++		INIT_LIST_HEAD(&client->xfrs);
++		sbefifo_put_client(client);
++		return ret;
++	}
++
++	n = min_t(size_t, n, len);
++
++	if (copy_to_user(buf, READ_ONCE(client->rbuf.rpos), n)) {
++		sbefifo_put_client(client);
++		return -EFAULT;
++	}
++
++	if (sbefifo_buf_readnb(&client->rbuf, n)) {
++		xfr = sbefifo_client_next_xfr(client);
++		if (!test_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags)) {
++			/*
++			 * Fill the read buffer back up.
++			 */
++			sbefifo_get(sbefifo);
++			if (mod_timer(&client->dev->poll_timer, jiffies))
++				sbefifo_put(sbefifo);
++		} else {
++			kfree(xfr);
++			list_del(&xfr->client);
++			wake_up(&sbefifo->wait);
++		}
++	}
++
++	sbefifo_put_client(client);
++
++	return n;
++}
++
++static ssize_t sbefifo_write(struct file *file, const char __user *buf,
++		size_t len, loff_t *offset)
++{
++	struct sbefifo_client *client = file->private_data;
++	struct sbefifo *sbefifo = client->dev;
++	struct sbefifo_xfr *xfr;
++	ssize_t ret = 0;
++	size_t n;
++
++	WARN_ON(*offset);
++
++	if (!access_ok(VERIFY_READ, buf, len))
++		return -EFAULT;
++
++	if ((len >> 2) << 2 != len)
++		return -EINVAL;
++
++	if (!len)
++		return 0;
++
++	n = sbefifo_buf_nbwriteable(&client->wbuf);
++
++	spin_lock_irq(&sbefifo->lock);
++	xfr = sbefifo_next_xfr(sbefifo);
++
++	if ((file->f_flags & O_NONBLOCK) && xfr && n < len) {
++		spin_unlock_irq(&sbefifo->lock);
++		return -EAGAIN;
++	}
++
++	xfr = sbefifo_enq_xfr(client);
++	if (!xfr) {
++		spin_unlock_irq(&sbefifo->lock);
++		return -ENOMEM;
++	}
++	spin_unlock_irq(&sbefifo->lock);
++
++	sbefifo_get_client(client);
++
++	/*
++	 * Partial writes are not really allowed in that EOT is sent exactly
++	 * once per write.
++	 */
++	while (len) {
++		if (wait_event_interruptible(sbefifo->wait,
++				READ_ONCE(sbefifo->rc) ||
++					(sbefifo_client_next_xfr(client) == xfr &&
++					 (n = sbefifo_buf_nbwriteable(
++							&client->wbuf))))) {
++			set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
++			sbefifo_get(sbefifo);
++			if (mod_timer(&sbefifo->poll_timer, jiffies))
++				sbefifo_put(sbefifo);
++
++			sbefifo_put_client(client);
++			return -ERESTARTSYS;
++		}
++		if (sbefifo->rc) {
++			INIT_LIST_HEAD(&client->xfrs);
++			sbefifo_put_client(client);
++			return sbefifo->rc;
++		}
++
++		n = min_t(size_t, n, len);
++
++		if (copy_from_user(READ_ONCE(client->wbuf.wpos), buf, n)) {
++			set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
++			sbefifo_get(sbefifo);
++			if (mod_timer(&sbefifo->poll_timer, jiffies))
++				sbefifo_put(sbefifo);
++			sbefifo_put_client(client);
++			return -EFAULT;
++		}
++
++		sbefifo_buf_wrotenb(&client->wbuf, n);
++		len -= n;
++		buf += n;
++		ret += n;
++
++		/*
++		 * Drain the write buffer.
++		 */
++		sbefifo_get(sbefifo);
++		if (mod_timer(&client->dev->poll_timer, jiffies))
++			sbefifo_put(sbefifo);
++	}
++
++	set_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags);
++	sbefifo_put_client(client);
++
++	return ret;
++}
++
++static int sbefifo_release(struct inode *inode, struct file *file)
++{
++	struct sbefifo_client *client = file->private_data;
++	struct sbefifo *sbefifo = client->dev;
++
++	sbefifo_put_client(client);
++
++	return READ_ONCE(sbefifo->rc);
++}
++
++static const struct file_operations sbefifo_fops = {
++	.owner		= THIS_MODULE,
++	.open		= sbefifo_open,
++	.read		= sbefifo_read,
++	.write		= sbefifo_write,
++	.poll		= sbefifo_poll,
++	.release	= sbefifo_release,
++};
++
++static int sbefifo_probe(struct device *dev)
++{
++	struct fsi_device *fsi_dev = to_fsi_dev(dev);
++	struct sbefifo *sbefifo;
++	u32 sts;
++	int ret;
++
++	dev_info(dev, "Found sbefifo device\n");
++	sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL);
++	if (!sbefifo)
++		return -ENOMEM;
++
++	sbefifo->fsi_dev = fsi_dev;
++
++	ret = sbefifo_inw(sbefifo,
++			SBEFIFO_UP | SBEFIFO_STS, &sts);
++	if (ret)
++		return ret;
++	if (!(sts & SBEFIFO_EMPTY)) {
++		dev_err(&sbefifo->fsi_dev->dev,
++				"Found data in upstream fifo\n");
++		return -EIO;
++	}
++
++	ret = sbefifo_inw(sbefifo, SBEFIFO_DWN | SBEFIFO_STS, &sts);
++	if (ret)
++		return ret;
++	if (!(sts & SBEFIFO_EMPTY)) {
++		dev_err(&sbefifo->fsi_dev->dev,
++				"Found data in downstream fifo\n");
++		return -EIO;
++	}
++
++	sbefifo->mdev.minor = MISC_DYNAMIC_MINOR;
++	sbefifo->mdev.fops = &sbefifo_fops;
++	sbefifo->mdev.name = sbefifo->name;
++	sbefifo->mdev.parent = dev;
++	spin_lock_init(&sbefifo->lock);
++	kref_init(&sbefifo->kref);
++
++	sbefifo->idx = ida_simple_get(&sbefifo_ida, 1, INT_MAX, GFP_KERNEL);
++	snprintf(sbefifo->name, sizeof(sbefifo->name), "sbefifo%d",
++			sbefifo->idx);
++	init_waitqueue_head(&sbefifo->wait);
++	INIT_LIST_HEAD(&sbefifo->xfrs);
++
++	/* This bit of silicon doesn't offer any interrupts... */
++	setup_timer(&sbefifo->poll_timer, sbefifo_poll_timer,
++			(unsigned long)sbefifo);
++
++	list_add(&sbefifo->link, &sbefifo_fifos);
++	return misc_register(&sbefifo->mdev);
++}
++
++static int sbefifo_remove(struct device *dev)
++{
++	struct fsi_device *fsi_dev = to_fsi_dev(dev);
++	struct sbefifo *sbefifo, *sbefifo_tmp;
++	struct sbefifo_xfr *xfr;
++
++	list_for_each_entry_safe(sbefifo, sbefifo_tmp, &sbefifo_fifos, link) {
++		if (sbefifo->fsi_dev != fsi_dev)
++			continue;
++		misc_deregister(&sbefifo->mdev);
++		list_del(&sbefifo->link);
++		ida_simple_remove(&sbefifo_ida, sbefifo->idx);
++
++		if (del_timer_sync(&sbefifo->poll_timer))
++			sbefifo_put(sbefifo);
++
++		spin_lock(&sbefifo->lock);
++		list_for_each_entry(xfr, &sbefifo->xfrs, xfrs)
++			kfree(xfr);
++		spin_unlock(&sbefifo->lock);
++
++		WRITE_ONCE(sbefifo->rc, -ENODEV);
++
++		wake_up(&sbefifo->wait);
++		sbefifo_put(sbefifo);
++	}
++
++	return 0;
++}
++
++static struct fsi_device_id sbefifo_ids[] = {
++	{
++		.engine_type = FSI_ENGID_SBE,
++		.version = FSI_VERSION_ANY,
++	},
++	{ 0 }
++};
++
++static struct fsi_driver sbefifo_drv = {
++	.id_table = sbefifo_ids,
++	.drv = {
++		.name = DEVICE_NAME,
++		.bus = &fsi_bus_type,
++		.probe = sbefifo_probe,
++		.remove = sbefifo_remove,
++	}
++};
++
++static int sbefifo_init(void)
++{
++	INIT_LIST_HEAD(&sbefifo_fifos);
++	return fsi_driver_register(&sbefifo_drv);
++}
++
++static void sbefifo_exit(void)
++{
++	fsi_driver_unregister(&sbefifo_drv);
++}
++
++module_init(sbefifo_init);
++module_exit(sbefifo_exit);
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Brad Bishop <bradleyb@fuzziesquirrel.com>");
++MODULE_DESCRIPTION("Linux device interface to the POWER self boot engine");