diff mbox

[3/3] remoteproc: add CSRatlas7 remoteproc driver

Message ID 1442295429-19137-4-git-send-email-21cnbao@gmail.com
State New, archived
Headers show

Commit Message

Barry Song Sept. 15, 2015, 5:37 a.m. UTC
From: Wei Chen <Wei.Chen@csr.com>

In CSRaltas7, Cortex-A7 uses this proc to communicate with Cortex-M3.
But M3 doesn't have to be a slave, it can boot indenpently or depend
on Linux to load firmware for it.

we reserve a memory for data and resource descriptors in DRAM.

Signed-off-by: Wei Chen <Wei.Chen@csr.com>
Signed-off-by: Barry Song <Baohua.Song@csr.com>
---
 .../bindings/remoteproc/sirf,remoteproc.txt        |  33 ++
 drivers/remoteproc/Kconfig                         |  13 +
 drivers/remoteproc/Makefile                        |   1 +
 drivers/remoteproc/sirf_remoteproc.c               | 467 +++++++++++++++++++++
 4 files changed, 514 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/remoteproc/sirf,remoteproc.txt
 create mode 100644 drivers/remoteproc/sirf_remoteproc.c
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/remoteproc/sirf,remoteproc.txt b/Documentation/devicetree/bindings/remoteproc/sirf,remoteproc.txt
new file mode 100644
index 0000000..409fb40
--- /dev/null
+++ b/Documentation/devicetree/bindings/remoteproc/sirf,remoteproc.txt
@@ -0,0 +1,33 @@ 
+SIRF Atlas7 Remote processor Device Binding
+------------------------------------------------
+1) Main node
+	Required properties :
+
+	- compatible : "sirf,atlas7-rproc"
+
+	- reg : register address of remoteproc device
+
+	- interrupts: the irq number this rproc need to handle.
+
+	- hwlocks: the hwlocks this rproc to used to protect data
+	  between two processors.
+
+	- memory-region: the memory region, which is used to store virtual
+	  device info, fifo buffers and share memory between two processors.
+
+	- firmware: the firmware file that will be loaded to remote processor.
+
+Please refer to ../reserved-memory/reserved-memory.txt for details of the
+memory-region bindings.
+Please refer to ../hwlock/hwlock.txt for details of the hwlock bindings.
+
+2) Example:
+			ns_m3_rproc@0 {
+				compatible = "sirf,atlas7-rproc";
+				reg = <0x13240108 0x4>,
+					<0x13240208 0x4>;
+				interrupts = <0 123 0>;
+				hwlocks = <&hwlock 0>, <&hwlock 1>;
+				memory-region = <&ipc_mem0>;
+				firmware = "RTOSDemo.bin";
+			};
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 28c711f..aeabbfa 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -77,4 +77,17 @@  config DA8XX_REMOTEPROC
 	  It's safe to say n here if you're not interested in multimedia
 	  offloading.
 
+config SIRF_REMOTEPROC
+	tristate "CSR atals7 remoteproc support"
+	depends on ARCH_ATLAS7
+	select REMOTEPROC
+	select RPMSG
+	default y
+	help
+	  Say y or m here to support CSR Atlas7 Inter-Processors
+	  Communication driver via remote processor framework.
+
+	  This can be either built-in or a loadable module.
+	  If unsure say N.
+
 endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 81b04d1..8cc4790 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -11,3 +11,4 @@  obj-$(CONFIG_OMAP_REMOTEPROC)		+= omap_remoteproc.o
 obj-$(CONFIG_STE_MODEM_RPROC)	 	+= ste_modem_rproc.o
 obj-$(CONFIG_WKUP_M3_RPROC)		+= wkup_m3_rproc.o
 obj-$(CONFIG_DA8XX_REMOTEPROC)		+= da8xx_remoteproc.o
+obj-$(CONFIG_SIRF_REMOTEPROC)	+= sirf_remoteproc.o
diff --git a/drivers/remoteproc/sirf_remoteproc.c b/drivers/remoteproc/sirf_remoteproc.c
new file mode 100644
index 0000000..cb7568d
--- /dev/null
+++ b/drivers/remoteproc/sirf_remoteproc.c
@@ -0,0 +1,467 @@ 
+/*
+ * SIRF Remote processor machine-specific module
+ *
+ * Copyright (c) 2014 Cambridge Silicon Radio Limited, a CSR plc group company.
+ *
+ * Licensed under GPLv2 or later.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/hwspinlock.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/remoteproc.h>
+
+#include "remoteproc_internal.h"
+
+struct fifo_buffer {
+	struct hwspinlock *lock;
+	unsigned char *buffer;
+	u32 w_pos;
+	u32 r_pos;
+	u32 size;
+	u32 *count; /* pointer to shared memory */
+};
+
+static int fifo_write(struct fifo_buffer *fifo,
+		const void *data, u32 len)
+{
+	int err;
+	u32 overflow, count;
+	ulong flags;
+
+	err = hwspin_lock_timeout_irqsave(fifo->lock, 100, &flags);
+	if (err) {
+		pr_err("%s, Get hwspinlock failed!err= %d\n",
+			__func__, err);
+		WARN_ON(err);
+		return -EBUSY;
+	}
+
+	if (len > fifo->size) {
+		err = -EFBIG;
+		goto err_exit;
+	}
+
+	count = *fifo->count;
+	overflow = len > (fifo->size - count);
+	if (overflow) {
+		/* previous data hasn't been read, FIFO busy */
+		err = -EBUSY;
+		goto err_exit;
+	}
+
+	/* copy data to fifo buffer */
+	memcpy(fifo->buffer + fifo->w_pos, data, len);
+	/* update fifo position */
+	fifo->w_pos = (fifo->w_pos + len) % fifo->size;
+	*fifo->count = count + len;
+
+	hwspin_unlock_irqrestore(fifo->lock, &flags);
+	err = 0;
+
+err_exit:
+	return err;
+}
+
+static int fifo_read(struct fifo_buffer *fifo,
+		void *data, u32 len)
+{
+	int err;
+	u32 count;
+
+	err = hwspin_lock_timeout(fifo->lock, 100);
+	if (err) {
+		pr_err("%s, Get hwspinlock failed!err= %d\n",
+			__func__, err);
+			WARN_ON(err);
+		return err;
+	}
+
+	count = *fifo->count;
+	if (!count) {
+		err = -ENOSPC;
+		goto err_exit;
+	}
+
+	if (len > count)
+		len = count;
+
+	/* copy data from fifo buffer */
+	memcpy(data, fifo->buffer + fifo->r_pos, len);
+	/* update fifo position */
+	fifo->r_pos = (fifo->r_pos + len) % fifo->size;
+	*fifo->count = count - len;
+
+	err = 0;
+
+err_exit:
+	hwspin_unlock(fifo->lock);
+
+	return err;
+}
+
+static int fifo_init(struct fifo_buffer *fifo, void *buffer,
+			int size, int hwlock_id)
+{
+
+	fifo->lock = hwspin_lock_request_specific(hwlock_id);
+	if (!fifo->lock) {
+		pr_err("%s:Could not request specific hwspin lock!\n",
+			__func__);
+		return -ENODEV;
+	}
+
+	fifo->count = (u32 *)buffer;
+	fifo->buffer = (unsigned char *)(buffer + sizeof(u32));
+	fifo->w_pos = 0;
+	fifo->r_pos = 0;
+	fifo->size = size - sizeof(u32);
+	*fifo->count = 0;
+
+	return 0;
+}
+
+/* FIFO shared memory has been divide into 2 logical channels */
+#define FIFO_LOGIC_CHN_0	0
+#define FIFO_LOGIC_CHN_1	1
+
+#define RPROC_DEF_FIFO_SIZE	0x1000
+
+/**
+ * struct sirf_rproc - SIRF remote processor instance state
+ * @rproc: rproc handle
+ * @rsc_dma: the dma address of the resource memory, include fifo.
+ * @rsc_size: resource memory size.
+ * @table_ptr: the virtual address of rproc resource table area.
+ * @table_len: the length of rproc resource table.
+ * @fifo_rx_lock: lock for fifo receive data.
+ * @fifo_tx_lock: lock for fifo send data.
+ * @tx_avail_wq: wait queue of send data when fifo is busy.
+ * @fifo_avail: fifo status for send data.
+ *		fifo can send data when fifo_avail is true.
+ * @fifo_msg_rx: memory address for fifo arrived data.
+ * @fifo_msg_tx: memory address for fifo send data.
+ * @fifo_iomemmem: iomem address for fifo register.
+ * @irq: the irq number of fifo allocated in backend OS.
+ * @irq_gen_count: generate IRQ counter for statistic
+ * @irq_get_count: arrive IRQ counter for statistic
+ */
+struct sirf_rproc {
+	struct rproc *rproc;
+	void *rsc_dma;
+	size_t rsc_size;
+	struct resource_table *table_ptr;
+	u32 table_len;
+	void __iomem *set_reg;
+	void __iomem *clr_reg;
+	struct fifo_buffer w_fifo;
+	struct fifo_buffer r_fifo;
+	u32 w_fifo_hwlock;
+	u32 r_fifo_hwlock;
+	int irq;
+};
+
+/* Interrupt handler for IRQs from remote processor */
+static irqreturn_t sirf_rproc_ipc_isr(int irq, void *data)
+{
+	struct rproc *rproc = (struct rproc *)data;
+	struct sirf_rproc *srproc = (struct sirf_rproc *)rproc->priv;
+	u32 notifyid;
+	int err;
+
+	/* clear interrupt */
+	readl(srproc->clr_reg);
+
+	do {
+		err = fifo_read(&srproc->r_fifo, &notifyid, sizeof(notifyid));
+		if (err)
+			break;
+		rproc_vq_interrupt(rproc, notifyid);
+	} while (1);
+
+	return IRQ_HANDLED;
+}
+
+static void sirf_rproc_kick(struct rproc *rproc, int notify_id)
+{
+	struct sirf_rproc *srproc = rproc->priv;
+	int ret;
+
+	ret = fifo_write(&srproc->w_fifo, &notify_id, sizeof(notify_id));
+	if (ret) {
+		dev_err(&rproc->dev,
+			"%s could not completed, err=%d\n",
+			__func__, ret);
+		WARN_ON(1);
+	}
+
+	/*
+	 * Trigger interrupt to ask remote side to get new added data
+	 * or handle the data already in the FIFO as fast as possible.
+	 */
+	smp_mb();
+
+	writel(0x01, srproc->set_reg);
+}
+
+static const struct of_device_id sirf_rproc_dt_ids[] = {
+	{ .compatible = "sirf,atlas7-rproc", },
+	{},
+};
+
+static struct rproc_ops sirf_rproc_ops = {
+	.kick = sirf_rproc_kick,
+};
+
+static struct resource_table *
+srproc_fw_find_rsc_table(struct rproc *rproc,
+				const struct firmware *fw,
+				int *tablesz)
+{
+	struct sirf_rproc *srproc = (struct sirf_rproc *)rproc->priv;
+
+	*tablesz = srproc->table_len;
+	return srproc->table_ptr;
+}
+
+struct rproc_fw_ops sirf_rproc_fw_ops = {
+	.find_rsc_table = srproc_fw_find_rsc_table,
+};
+
+static int sirf_rproc_parse_memory(struct platform_device *pdev,
+				struct sirf_rproc *srproc)
+{
+	struct device_node *m_node;
+	struct resource res;
+	void *rsc_addr;
+	size_t rsc_size;
+	int ret;
+
+	m_node = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
+	if (!m_node)
+		return -ENODEV;
+
+	ret = of_address_to_resource(m_node, 0, &res);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"Convert address to resource failed! ret=%d\n",
+			ret);
+		return ret;
+	}
+
+	rsc_addr = (void *)__phys_to_virt(res.start);
+	rsc_size = res.end - res.start + 1;
+
+	/* create a coherent mapping */
+	srproc->rsc_dma = dma_common_contiguous_remap(virt_to_page(rsc_addr),
+				rsc_size, VM_IO,
+				pgprot_dmacoherent(PAGE_KERNEL),
+				NULL);
+	if (!srproc->rsc_dma)
+		return -ENOMEM;
+
+	srproc->rsc_size = rsc_size;
+
+	return 0;
+}
+
+static int sirf_rproc_parse_args(struct platform_device *pdev,
+				struct sirf_rproc *srproc)
+{
+	void *tx_buffer, *rx_buffer;
+	struct resource *res;
+	int ret;
+
+	/* retrieve trigger interrupt io base */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	srproc->set_reg = devm_ioremap_resource(&pdev->dev, res);
+	if (!srproc->set_reg) {
+		dev_err(&pdev->dev,
+			"Unable to map rproc trigger interrupt registers!\n");
+		return -ENOMEM;
+	}
+
+	/* retrieve clear interrupt io base */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	srproc->clr_reg = devm_ioremap_resource(&pdev->dev, res);
+	if (!srproc->clr_reg) {
+		dev_err(&pdev->dev,
+			"Unable to map rproc clear interrupt registers!\n");
+		return -ENOMEM;
+	}
+
+	ret = of_irq_get(pdev->dev.of_node, 0);
+	if (ret == -EPROBE_DEFER) {
+		dev_err(&pdev->dev,
+			"Unable to find IRQ number. ret=%d\n", ret);
+		return ret;
+	}
+	srproc->irq = ret;
+
+	/* Request hwlocks for rproc */
+	srproc->w_fifo_hwlock = of_hwspin_lock_get_id(pdev->dev.of_node, 0);
+	if (srproc->w_fifo_hwlock < 0) {
+		ret = srproc->w_fifo_hwlock;
+		dev_err(&pdev->dev,
+			"Unable to get hwlock for write fifo. ret=%d\n", ret);
+		goto failed;
+	}
+
+	srproc->r_fifo_hwlock = of_hwspin_lock_get_id(pdev->dev.of_node, 1);
+	if (srproc->r_fifo_hwlock < 0) {
+		ret = srproc->r_fifo_hwlock;
+		dev_err(&pdev->dev,
+			"Unable to get hwlock for read fifo. ret=%d\n", ret);
+		goto failed;
+	}
+
+	/* Parse share memory information */
+	ret = sirf_rproc_parse_memory(pdev, srproc);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"Unable to setup ipc share memory info. ret=%d\n",
+			ret);
+		goto failed;
+	}
+	srproc->table_ptr = srproc->rsc_dma;
+
+	/* check resource table size */
+	if (RPROC_DEF_FIFO_SIZE * 2 >= srproc->rsc_size) {
+		dev_err(&pdev->dev,
+			"There is no memory left for resource table!\n");
+		ret = -EINVAL;
+		goto free_rsc;
+	}
+
+	if (srproc->table_ptr->ver != 1) {
+		dev_err(&pdev->dev,
+			"unsupported fw ver: %d\n",
+			srproc->table_ptr->ver);
+		ret = -EINVAL;
+		goto free_rsc;
+	}
+
+	srproc->table_len = srproc->rsc_size - RPROC_DEF_FIFO_SIZE * 2;
+	tx_buffer = srproc->rsc_dma + srproc->table_len +
+		RPROC_DEF_FIFO_SIZE * FIFO_LOGIC_CHN_0;
+	rx_buffer = srproc->rsc_dma + srproc->table_len +
+		RPROC_DEF_FIFO_SIZE * FIFO_LOGIC_CHN_1;
+
+	ret = fifo_init(&srproc->w_fifo, tx_buffer,
+			RPROC_DEF_FIFO_SIZE, srproc->w_fifo_hwlock);
+	if (ret)
+		goto free_rsc;
+
+	ret = fifo_init(&srproc->r_fifo, rx_buffer,
+			RPROC_DEF_FIFO_SIZE, srproc->r_fifo_hwlock);
+	if (ret)
+		goto free_rsc;
+
+	return 0;
+
+free_rsc:
+	dma_common_free_remap(srproc->rsc_dma,
+			srproc->rsc_size, VM_IO);
+	srproc->table_ptr = NULL;
+	srproc->rsc_dma = NULL;
+
+failed:
+	return ret;
+}
+
+static int sirf_rproc_remove(struct platform_device *pdev)
+{
+	struct rproc *rproc = platform_get_drvdata(pdev);
+	struct sirf_rproc *srproc = rproc->priv;
+
+	dma_common_free_remap(srproc->rsc_dma,
+				srproc->rsc_size, VM_IO);
+
+	rproc->table_ptr = 0;
+
+	rproc_del(rproc);
+	rproc_put(rproc);
+
+	return 0;
+}
+
+static int sirf_rproc_probe(struct platform_device *pdev)
+{
+	struct sirf_rproc *srproc;
+	struct rproc *rproc;
+	const char *fw;
+	int ret;
+
+	ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_err(&pdev->dev, "dma_set_coherent_mask: %d\n", ret);
+		return ret;
+	}
+
+	ret = of_property_read_string(pdev->dev.of_node, "firmware", &fw);
+	if (ret)
+		fw = NULL; /* Set to NULL, rproc core will use default name */
+
+	rproc = rproc_alloc(&pdev->dev, dev_name(&pdev->dev), &sirf_rproc_ops,
+				fw, sizeof(*srproc));
+	if (!rproc)
+		return -ENOMEM;
+
+	srproc = rproc->priv;
+	srproc->rproc = rproc;
+	/* Setup sirf rproc firmware ops */
+	rproc->fw_ops = &sirf_rproc_fw_ops;
+	/* This rproc is always on */
+	rproc->state = RPROC_ALWAYS_ON;
+
+	ret = sirf_rproc_parse_args(pdev, srproc);
+	if (ret)
+		goto free_rproc;
+
+	ret = devm_request_threaded_irq(&rproc->dev, srproc->irq,
+				NULL, sirf_rproc_ipc_isr,
+				IRQF_ONESHOT,
+				dev_name(&pdev->dev), rproc);
+	if (ret) {
+		dev_err(&rproc->dev,
+			"request_threaded_irq %d error: %d\n",
+			srproc->irq, ret);
+		goto free_rproc;
+	}
+
+	ret = rproc_add(rproc);
+	if (ret) {
+		dev_err(&rproc->dev, "rproc_add failed: %d\n", ret);
+		goto free_rproc;
+	}
+
+	platform_set_drvdata(pdev, rproc);
+
+	return 0;
+
+free_rproc:
+	rproc_put(rproc);
+
+	return ret;
+}
+
+static struct platform_driver sirf_rproc_driver = {
+	.probe = sirf_rproc_probe,
+	.remove = sirf_rproc_remove,
+	.driver = {
+		.name = "sirfsoc_remoteproc",
+		.of_match_table = of_match_ptr(sirf_rproc_dt_ids),
+	},
+};
+module_platform_driver(sirf_rproc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SIRF Remote Processor driver");