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-2-3-drivers-fsi-sbefifo-Add-OCC-driver.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-2-3-drivers-fsi-sbefifo-Add-OCC-driver.patch
new file mode 100644
index 0000000..5c53dad
--- /dev/null
+++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-2-3-drivers-fsi-sbefifo-Add-OCC-driver.patch
@@ -0,0 +1,694 @@
+From patchwork Fri May 12 19:38:19 2017
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Subject: [linux,dev-4.10,2/3] drivers: fsi: sbefifo: Add OCC driver
+From: eajames@linux.vnet.ibm.com
+X-Patchwork-Id: 761838
+Message-Id: <1494617900-32369-3-git-send-email-eajames@linux.vnet.ibm.com>
+To: openbmc@lists.ozlabs.org
+Cc: "Edward A. James" <eajames@us.ibm.com>, bradleyb@fuzziesquirrel.com,
+ cbostic@linux.vnet.ibm.com
+Date: Fri, 12 May 2017 14:38:19 -0500
+
+From: "Edward A. James" <eajames@us.ibm.com>
+
+This driver provides an atomic communications channel between the OCC on
+the POWER9 processor and a service processor (a BMC). The driver is
+dependent on the FSI SBEIFO driver to get hardware access to the OCC
+SRAM.
+
+The format of the communication is a command followed by a response.
+Since the command and response must be performed atomically, the driver
+will perform this operations asynchronously. In this way, a write
+operation starts the command, and a read will gather the response data
+once it is complete.
+
+Signed-off-by: Edward A. James <eajames@us.ibm.com>
+---
+ drivers/fsi/Kconfig  |   9 +
+ drivers/fsi/Makefile |   1 +
+ drivers/fsi/occ.c    | 625 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 635 insertions(+)
+ create mode 100644 drivers/fsi/occ.c
+
+diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
+index 39527fa..f3d8593 100644
+--- a/drivers/fsi/Kconfig
++++ b/drivers/fsi/Kconfig
+@@ -36,6 +36,15 @@ config FSI_SBEFIFO
+ 	---help---
+ 	This option enables an FSI based SBEFIFO device driver.
+ 
++if FSI_SBEFIFO
++
++config OCCFIFO
++	tristate "OCC SBEFIFO client device driver"
++	---help---
++	This option enables an SBEFIFO based OCC device driver.
++
++endif
++
+ endif
+ 
+ endmenu
+diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
+index 851182e..336d9d5 100644
+--- a/drivers/fsi/Makefile
++++ b/drivers/fsi/Makefile
+@@ -4,3 +4,4 @@ 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
++obj-$(CONFIG_OCCFIFO) += occ.o
+diff --git a/drivers/fsi/occ.c b/drivers/fsi/occ.c
+new file mode 100644
+index 0000000..74272c8
+--- /dev/null
++++ b/drivers/fsi/occ.c
+@@ -0,0 +1,625 @@
++/*
++ * Copyright 2017 IBM Corp.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ */
++
++#include <asm/unaligned.h>
++#include <linux/device.h>
++#include <linux/err.h>
++#include <linux/fsi-sbefifo.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/uaccess.h>
++#include <linux/wait.h>
++#include <linux/workqueue.h>
++
++#define OCC_SRAM_BYTES		4096
++#define OCC_CMD_DATA_BYTES	4090
++#define OCC_RESP_DATA_BYTES	4089
++
++struct occ {
++	struct device *sbefifo;
++	char name[32];
++	int idx;
++	struct miscdevice mdev;
++	struct list_head xfrs;
++	spinlock_t list_lock;
++	spinlock_t occ_lock;
++	struct work_struct work;
++};
++
++#define to_occ(x)	container_of((x), struct occ, mdev)
++
++struct occ_command {
++	u8 seq_no;
++	u8 cmd_type;
++	u16 data_length;
++	u8 data[OCC_CMD_DATA_BYTES];
++	u16 checksum;
++};
++
++struct occ_response {
++	u8 seq_no;
++	u8 cmd_type;
++	u8 return_status;
++	u16 data_length;
++	u8 data[OCC_RESP_DATA_BYTES];
++	u16 checksum;
++};
++
++struct occ_xfr;
++
++enum {
++	CLIENT_NONBLOCKING,
++};
++
++struct occ_client {
++	struct occ *occ;
++	struct occ_xfr *xfr;
++	spinlock_t lock;
++	wait_queue_head_t wait;
++	size_t read_offset;
++	unsigned long flags;
++};
++
++enum {
++	XFR_IN_PROGRESS,
++	XFR_COMPLETE,
++	XFR_CANCELED,
++	XFR_WAITING,
++};
++
++struct occ_xfr {
++	struct list_head link;
++	struct occ_client *client;
++	int rc;
++	u8 buf[OCC_SRAM_BYTES];
++	size_t cmd_data_length;
++	size_t resp_data_length;
++	unsigned long flags;
++};
++
++static struct workqueue_struct *occ_wq;
++
++static DEFINE_IDA(occ_ida);
++
++static void occ_enqueue_xfr(struct occ_xfr *xfr)
++{
++	int empty;
++	struct occ *occ = xfr->client->occ;
++
++	spin_lock_irq(&occ->list_lock);
++	empty = list_empty(&occ->xfrs);
++	list_add_tail(&xfr->link, &occ->xfrs);
++	spin_unlock(&occ->list_lock);
++
++	if (empty)
++		queue_work(occ_wq, &occ->work);
++}
++
++static int occ_open(struct inode *inode, struct file *file)
++{
++	struct occ_client *client;
++	struct miscdevice *mdev = file->private_data;
++	struct occ *occ = to_occ(mdev);
++
++	client = kzalloc(sizeof(*client), GFP_KERNEL);
++	if (!client)
++		return -ENOMEM;
++
++	client->occ = occ;
++	spin_lock_init(&client->lock);
++	init_waitqueue_head(&client->wait);
++
++	if (file->f_flags & O_NONBLOCK)
++		set_bit(CLIENT_NONBLOCKING, &client->flags);
++
++	file->private_data = client;
++
++	return 0;
++}
++
++static ssize_t occ_read(struct file *file, char __user *buf, size_t len,
++			loff_t *offset)
++{
++	int rc;
++	size_t bytes;
++	struct occ_xfr *xfr;
++	struct occ_client *client = file->private_data;
++
++	if (!access_ok(VERIFY_WRITE, buf, len))
++		return -EFAULT;
++
++	if (len > OCC_SRAM_BYTES)
++		return -EINVAL;
++
++	spin_lock_irq(&client->lock);
++	if (!client->xfr) {
++		/* we just finished reading all data, return 0 */
++		if (client->read_offset) {
++			rc = 0;
++			client->read_offset = 0;
++		} else
++			rc = -ENOMSG;
++
++		goto done;
++	}
++
++	xfr = client->xfr;
++
++	if (!test_bit(XFR_COMPLETE, &xfr->flags)) {
++		if (client->flags & CLIENT_NONBLOCKING) {
++			rc = -ERESTARTSYS;
++			goto done;
++		}
++
++		set_bit(XFR_WAITING, &xfr->flags);
++		spin_unlock(&client->lock);
++
++		rc = wait_event_interruptible(client->wait,
++			test_bit(XFR_COMPLETE, &xfr->flags) ||
++			test_bit(XFR_CANCELED, &xfr->flags));
++
++		spin_lock_irq(&client->lock);
++		if (test_bit(XFR_CANCELED, &xfr->flags)) {
++			kfree(xfr);
++			spin_unlock(&client->lock);
++			kfree(client);
++			return -EBADFD;
++		}
++
++		clear_bit(XFR_WAITING, &xfr->flags);
++		if (!test_bit(XFR_COMPLETE, &xfr->flags)) {
++			rc = -EINTR;
++			goto done;
++		}
++	}
++
++	if (xfr->rc) {
++		rc = xfr->rc;
++		goto done;
++	}
++
++	bytes = min(len, xfr->resp_data_length - client->read_offset);
++	if (copy_to_user(buf, &xfr->buf[client->read_offset], bytes)) {
++		rc = -EFAULT;
++		goto done;
++	}
++
++	client->read_offset += bytes;
++
++	/* xfr done */
++	if (client->read_offset == xfr->resp_data_length) {
++		kfree(xfr);
++		client->xfr = NULL;
++	}
++
++	rc = bytes;
++
++done:
++	spin_unlock(&client->lock);
++	return rc;
++}
++
++static ssize_t occ_write(struct file *file, const char __user *buf,
++			 size_t len, loff_t *offset)
++{
++	int rc;
++	struct occ_xfr *xfr;
++	struct occ_client *client = file->private_data;
++
++	if (!access_ok(VERIFY_READ, buf, len))
++		return -EFAULT;
++
++	if (len > OCC_SRAM_BYTES)
++		return -EINVAL;
++
++	spin_lock_irq(&client->lock);
++	if (client->xfr) {
++		rc = -EDEADLK;
++		goto done;
++	}
++
++	xfr = kzalloc(sizeof(*xfr), GFP_KERNEL);
++	if (!xfr) {
++		rc = -ENOMEM;
++		goto done;
++	}
++
++	if (copy_from_user(xfr->buf, buf, len)) {
++		kfree(xfr);
++		rc = -EFAULT;
++		goto done;
++	}
++
++	xfr->client = client;
++	xfr->cmd_data_length = len;
++	client->xfr = xfr;
++	client->read_offset = 0;
++
++	occ_enqueue_xfr(xfr);
++
++	rc = len;
++
++done:
++	spin_unlock(&client->lock);
++	return rc;
++}
++
++static int occ_release(struct inode *inode, struct file *file)
++{
++	struct occ_xfr *xfr;
++	struct occ_client *client = file->private_data;
++	struct occ *occ = client->occ;
++
++	spin_lock_irq(&client->lock);
++	xfr = client->xfr;
++	if (!xfr) {
++		spin_unlock(&client->lock);
++		kfree(client);
++		return 0;
++	}
++
++	spin_lock_irq(&occ->list_lock);
++	set_bit(XFR_CANCELED, &xfr->flags);
++	if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) {
++		/* already deleted from list if complete */
++		if (!test_bit(XFR_COMPLETE, &xfr->flags))
++			list_del(&xfr->link);
++
++		spin_unlock(&occ->list_lock);
++
++		if (test_bit(XFR_WAITING, &xfr->flags)) {
++			/* blocking read; let reader clean up */
++			wake_up_interruptible(&client->wait);
++			spin_unlock(&client->lock);
++			return 0;
++		}
++
++		kfree(xfr);
++		spin_unlock(&client->lock);
++		kfree(client);
++		return 0;
++	}
++
++	/* operation is in progress; let worker clean up*/
++	spin_unlock(&occ->list_lock);
++	spin_unlock(&client->lock);
++	return 0;
++}
++
++static const struct file_operations occ_fops = {
++	.owner = THIS_MODULE,
++	.open = occ_open,
++	.read = occ_read,
++	.write = occ_write,
++	.release = occ_release,
++};
++
++static int occ_getscom(struct device *sbefifo, u32 address, u8 *data)
++{
++	int rc;
++	u32 buf[4];
++	struct sbefifo_client *client;
++	const size_t len = sizeof(buf);
++
++	buf[0] = cpu_to_be32(0x4);
++	buf[1] = cpu_to_be32(0xa201);
++	buf[2] = 0;
++	buf[3] = cpu_to_be32(address);
++
++	client = sbefifo_drv_open(sbefifo, 0);
++	if (!client)
++		return -ENODEV;
++
++	rc = sbefifo_drv_write(client, (const char *)buf, len);
++	if (rc < 0)
++		goto done;
++	else if (rc != len) {
++		rc = -EMSGSIZE;
++		goto done;
++	}
++
++	rc = sbefifo_drv_read(client, (char *)buf, len);
++	if (rc < 0)
++		goto done;
++	else if (rc != len) {
++		rc = -EMSGSIZE;
++		goto done;
++	}
++
++	/* check for good response */
++	if ((be32_to_cpu(buf[2]) >> 16) != 0xC0DE) {
++		rc = -EFAULT;
++		goto done;
++	}
++
++	rc = 0;
++
++	memcpy(data, buf, sizeof(u64));
++
++done:
++	sbefifo_drv_release(client);
++	return rc;
++}
++
++static int occ_putscom(struct device *sbefifo, u32 address, u8 *data)
++{
++	int rc;
++	u32 buf[6];
++	struct sbefifo_client *client;
++	const size_t len = sizeof(buf);
++
++	buf[0] = cpu_to_be32(0x6);
++	buf[1] = cpu_to_be32(0xa202);
++	buf[2] = 0;
++	buf[3] = cpu_to_be32(address);
++	memcpy(&buf[4], data, sizeof(u64));
++
++	client = sbefifo_drv_open(sbefifo, 0);
++	if (!client)
++		return -ENODEV;
++
++	rc = sbefifo_drv_write(client, (const char *)buf, len);
++	if (rc < 0)
++		goto done;
++	else if (rc != len) {
++		rc = -EMSGSIZE;
++		goto done;
++	}
++
++	rc = 0;
++
++	/* ignore response */
++	sbefifo_drv_read(client, (char *)buf, len);
++
++done:
++	sbefifo_drv_release(client);
++	return rc;
++}
++
++static int occ_putscom_u32(struct device *sbefifo, u32 address, u32 data0,
++			   u32 data1)
++{
++	u8 buf[8];
++	u32 raw_data0 = cpu_to_be32(data0), raw_data1 = cpu_to_be32(data1);
++
++	memcpy(buf, &raw_data0, 4);
++	memcpy(buf + 4, &raw_data1, 4);
++
++	return occ_putscom(sbefifo, address, buf);
++}
++
++static void occ_worker(struct work_struct *work)
++{
++	int i, empty, canceled, waiting, rc;
++	u16 resp_data_length;
++	struct occ *occ = container_of(work, struct occ, work);
++	struct device *sbefifo = occ->sbefifo;
++	struct occ_client *client;
++	struct occ_xfr *xfr;
++	struct occ_response *resp;
++
++again:
++	spin_lock_irq(&occ->list_lock);
++	xfr = list_first_entry(&occ->xfrs, struct occ_xfr, link);
++	if (!xfr) {
++		spin_unlock(&occ->list_lock);
++		return;
++	}
++
++	set_bit(XFR_IN_PROGRESS, &xfr->flags);
++	spin_unlock(&occ->list_lock);
++
++	resp = (struct occ_response *)xfr->buf;
++
++	spin_lock_irq(&occ->occ_lock);
++
++	/* set address reg to occ sram command buffer */
++	rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBE000, 0);
++	if (rc)
++		goto done;
++
++	/* write cmd data */
++	for (i = 0; i < xfr->cmd_data_length; i += 8) {
++		rc = occ_putscom(sbefifo, 0x6D055, &xfr->buf[i]);
++		if (rc)
++			goto done;
++	}
++
++	/* trigger attention */
++	rc = occ_putscom_u32(sbefifo, 0x6D035, 0x20010000, 0);
++	if (rc)
++		goto done;
++
++	/* set address reg to occ sram response buffer */
++	rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBF000, 0);
++	if (rc)
++		goto done;
++
++	rc = occ_getscom(sbefifo, 0x6D055, xfr->buf);
++	if (rc)
++		goto done;
++
++	xfr->resp_data_length += 8;
++
++	resp_data_length = be16_to_cpu(get_unaligned(&resp->data_length));
++	if (resp_data_length > OCC_RESP_DATA_BYTES) {
++		rc = -EFAULT;
++		goto done;
++	}
++
++	/* already read 3 bytes of resp data, but also need 2 bytes chksum */
++	for (i = 8; i < resp_data_length + 7; i += 8) {
++		rc = occ_getscom(sbefifo, 0x6D055, &xfr->buf[i]);
++		if (rc)
++			goto done;
++
++		xfr->resp_data_length += 8;
++	}
++
++	/* no errors, got all data */
++	xfr->resp_data_length = resp_data_length + 7;
++
++done:
++	spin_unlock(&occ->occ_lock);
++
++	xfr->rc = rc;
++	client = xfr->client;
++
++	/* lock client to prevent race with read() */
++	spin_lock_irq(&client->lock);
++	set_bit(XFR_COMPLETE, &xfr->flags);
++	waiting = test_bit(XFR_WAITING, &xfr->flags);
++	spin_unlock(&client->lock);
++
++	spin_lock_irq(&occ->list_lock);
++	clear_bit(XFR_IN_PROGRESS, &xfr->flags);
++	list_del(&xfr->link);
++	empty = list_empty(&occ->xfrs);
++	canceled = test_bit(XFR_CANCELED, &xfr->flags);
++	spin_unlock(&occ->list_lock);
++
++	if (waiting)
++		wake_up_interruptible(&client->wait);
++	else if (canceled) {
++		kfree(xfr);
++		kfree(xfr->client);
++	}
++
++	if (!empty)
++		goto again;
++}
++
++static int occ_probe(struct platform_device *pdev)
++{
++	int rc;
++	u32 reg;
++	struct occ *occ;
++	struct device *dev = &pdev->dev;
++
++	dev_info(dev, "Found occ device\n");
++	occ = devm_kzalloc(dev, sizeof(*occ), GFP_KERNEL);
++	if (!occ)
++		return -ENOMEM;
++
++	occ->sbefifo = dev->parent;
++	INIT_LIST_HEAD(&occ->xfrs);
++	spin_lock_init(&occ->list_lock);
++	spin_lock_init(&occ->occ_lock);
++	INIT_WORK(&occ->work, occ_worker);
++
++	if (dev->of_node) {
++		rc = of_property_read_u32(dev->of_node, "reg", &reg);
++		if (!rc) {
++			/* make sure we don't have a duplicate from dts */
++			occ->idx = ida_simple_get(&occ_ida, reg, reg + 1,
++						  GFP_KERNEL);
++			if (occ->idx < 0)
++				occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
++							  GFP_KERNEL);
++		} else
++			occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX,
++						  GFP_KERNEL);
++	} else
++		occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, GFP_KERNEL);
++
++	snprintf(occ->name, sizeof(occ->name), "occ%d", occ->idx);
++	occ->mdev.fops = &occ_fops;
++	occ->mdev.minor = MISC_DYNAMIC_MINOR;
++	occ->mdev.name = occ->name;
++	occ->mdev.parent = dev;
++
++	rc = misc_register(&occ->mdev);
++	if (rc) {
++		dev_err(dev, "failed to register miscdevice\n");
++		return rc;
++	}
++
++	platform_set_drvdata(pdev, occ);
++
++	return 0;
++}
++
++static int occ_remove(struct platform_device *pdev)
++{
++	struct occ_xfr *xfr, *tmp;
++	struct occ *occ = platform_get_drvdata(pdev);
++	struct occ_client *client;
++
++	misc_deregister(&occ->mdev);
++
++	spin_lock_irq(&occ->list_lock);
++	list_for_each_entry_safe(xfr, tmp, &occ->xfrs, link) {
++		client = xfr->client;
++		set_bit(XFR_CANCELED, &xfr->flags);
++
++		if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) {
++			list_del(&xfr->link);
++
++			spin_lock_irq(&client->lock);
++			if (test_bit(XFR_WAITING, &xfr->flags)) {
++				wake_up_interruptible(&client->wait);
++				spin_unlock(&client->lock);
++			} else {
++				kfree(xfr);
++				spin_unlock(&client->lock);
++				kfree(client);
++			}
++		}
++	}
++	spin_unlock(&occ->list_lock);
++
++	flush_work(&occ->work);
++
++	ida_simple_remove(&occ_ida, occ->idx);
++
++	return 0;
++}
++
++static const struct of_device_id occ_match[] = {
++	{ .compatible = "ibm,p9-occ" },
++	{ },
++};
++
++static struct platform_driver occ_driver = {
++	.driver = {
++		.name = "occ",
++		.of_match_table	= occ_match,
++	},
++	.probe	= occ_probe,
++	.remove = occ_remove,
++};
++
++static int occ_init(void)
++{
++	occ_wq = create_singlethread_workqueue("occ");
++	if (!occ_wq)
++		return -ENOMEM;
++
++	return platform_driver_register(&occ_driver);
++}
++
++static void occ_exit(void)
++{
++	destroy_workqueue(occ_wq);
++
++	platform_driver_unregister(&occ_driver);
++}
++
++module_init(occ_init);
++module_exit(occ_exit);
++
++MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
++MODULE_DESCRIPTION("BMC P9 OCC driver");
++MODULE_LICENSE("GPL");
++