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");