diff mbox

[v8,5/7] ARM: davinci: Add a remoteproc driver implementation for OMAP-L138 DSP

Message ID 1362782508-3166-6-git-send-email-rtivy@ti.com (mailing list archive)
State Changes Requested
Headers show

Commit Message

Tivy, Robert March 8, 2013, 10:41 p.m. UTC
Signed-off-by: Robert Tivy <rtivy@ti.com>
---
 drivers/remoteproc/Makefile           |    1 +
 drivers/remoteproc/da8xx_remoteproc.c |  323 +++++++++++++++++++++++++++++++++
 2 files changed, 324 insertions(+)
 create mode 100644 drivers/remoteproc/da8xx_remoteproc.c
diff mbox

Patch

diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 391b651..ac2ff75 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -9,3 +9,4 @@  remoteproc-y				+= remoteproc_virtio.o
 remoteproc-y				+= remoteproc_elf_loader.o
 obj-$(CONFIG_OMAP_REMOTEPROC)		+= omap_remoteproc.o
 obj-$(CONFIG_STE_MODEM_RPROC)	 	+= ste_modem_rproc.o
+obj-$(CONFIG_DA8XX_REMOTEPROC)		+= da8xx_remoteproc.o
diff --git a/drivers/remoteproc/da8xx_remoteproc.c b/drivers/remoteproc/da8xx_remoteproc.c
new file mode 100644
index 0000000..a40e72a
--- /dev/null
+++ b/drivers/remoteproc/da8xx_remoteproc.c
@@ -0,0 +1,323 @@ 
+/*
+ * Remote processor machine-specific module for DA8XX
+ *
+ * Copyright (C) 2013 Texas Instruments, Inc.
+ *
+ * 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.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+
+#include <mach/clock.h>   /* for davinci_clk_reset_assert/deassert() */
+
+#include "remoteproc_internal.h"
+
+static char *da8xx_fw_name;
+module_param(da8xx_fw_name, charp, S_IRUGO);
+MODULE_PARM_DESC(da8xx_fw_name,
+	"\n\t\tName of DSP firmware file in /lib/firmware");
+
+/*
+ * OMAP-L138 Technical References:
+ * http://www.ti.com/product/omap-l138
+ */
+#define SYSCFG_CHIPSIG0 BIT(0)
+#define SYSCFG_CHIPSIG1 BIT(1)
+#define SYSCFG_CHIPSIG2 BIT(2)
+#define SYSCFG_CHIPSIG3 BIT(3)
+#define SYSCFG_CHIPSIG4 BIT(4)
+
+/**
+ * struct da8xx_rproc - da8xx remote processor instance state
+ * @rproc: rproc handle
+ * @dsp_clk: placeholder for platform's DSP clk
+ * @ack_fxn: chip-specific ack function for ack'ing irq
+ * @irq_data: ack_fxn function parameter
+ * @chipsig: virt ptr to DSP interrupt registers (CHIPSIG & CHIPSIG_CLR)
+ * @bootreg: virt ptr to DSP boot address register (HOST1CFG)
+ * @irq: irq # used by this instance
+ */
+struct da8xx_rproc {
+	struct rproc *rproc;
+	struct clk *dsp_clk;
+	void (*ack_fxn)(struct irq_data *data);
+	struct irq_data *irq_data;
+	void __iomem *chipsig;
+	void __iomem *bootreg;
+	int irq;
+};
+
+/**
+ * handle_event() - inbound virtqueue message workqueue function
+ *
+ * This function is registered as a kernel thread and is scheduled by the
+ * kernel handler.
+ */
+static irqreturn_t handle_event(int irq, void *p)
+{
+	struct rproc *rproc = (struct rproc *)p;
+
+	/* Process incoming buffers on all our vrings */
+	rproc_vq_interrupt(rproc, -1);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * da8xx_rproc_callback() - inbound virtqueue message handler
+ *
+ * This handler is invoked directly by the kernel whenever the remote
+ * core (DSP) has modified the state of a virtqueue.  There is no
+ * "payload" message indicating the virtqueue index as is the case with
+ * mailbox-based implementations on OMAP4.  As such, this handler "polls"
+ * each known virtqueue index for every invocation.
+ */
+static irqreturn_t da8xx_rproc_callback(int irq, void *p)
+{
+	struct rproc *rproc = (struct rproc *)p;
+	struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
+	u32 chipsig;
+
+	chipsig = readl(drproc->chipsig);
+	if (chipsig & SYSCFG_CHIPSIG0) {
+		/* Clear interrupt level source */
+		writel(SYSCFG_CHIPSIG0, drproc->chipsig + 4);
+
+		/*
+		 * ACK intr to AINTC.
+		 *
+		 * It has already been ack'ed by the kernel before calling
+		 * this function, but since the ARM<->DSP interrupts in the
+		 * CHIPSIG register are "level" instead of "pulse" variety,
+		 * we need to ack it after taking down the level else we'll
+		 * be called again immediately after returning.
+		 */
+		drproc->ack_fxn(drproc->irq_data);
+
+		return IRQ_WAKE_THREAD;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int da8xx_rproc_start(struct rproc *rproc)
+{
+	struct device *dev = rproc->dev.parent;
+	struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
+	struct clk *dsp_clk = drproc->dsp_clk;
+
+	/* hw requires the start (boot) address be on 1KB boundary */
+	if (rproc->bootaddr & 0x3ff) {
+		dev_err(dev, "invalid boot address: must be aligned to 1KB\n");
+
+		return -EINVAL;
+	}
+	writel(rproc->bootaddr, drproc->bootreg);
+
+	clk_enable(dsp_clk);
+	davinci_clk_reset_deassert(dsp_clk);
+
+	return 0;
+}
+
+static int da8xx_rproc_stop(struct rproc *rproc)
+{
+	struct da8xx_rproc *drproc = rproc->priv;
+
+	clk_disable(drproc->dsp_clk);
+
+	return 0;
+}
+
+/* kick a virtqueue */
+static void da8xx_rproc_kick(struct rproc *rproc, int vqid)
+{
+	struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
+
+	/* Interupt remote proc */
+	writel(SYSCFG_CHIPSIG2, drproc->chipsig);
+}
+
+static struct rproc_ops da8xx_rproc_ops = {
+	.start = da8xx_rproc_start,
+	.stop = da8xx_rproc_stop,
+	.kick = da8xx_rproc_kick,
+};
+
+static int reset_assert(struct device *dev)
+{
+	struct clk *dsp_clk;
+
+	dsp_clk = clk_get(dev, NULL);
+	if (IS_ERR(dsp_clk)) {
+		dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk));
+		return PTR_RET(dsp_clk);
+	}
+	davinci_clk_reset_assert(dsp_clk);
+	clk_put(dsp_clk);
+
+	return 0;
+}
+
+static int da8xx_rproc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct da8xx_rproc *drproc;
+	struct rproc *rproc;
+	struct irq_data *irq_data;
+	struct resource *bootreg_res;
+	struct resource *chipsig_res;
+	struct clk *dsp_clk;
+	void __iomem *chipsig;
+	void __iomem *bootreg;
+	int irq;
+	int ret;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "platform_get_irq(pdev, 0) error: %d\n", irq);
+		return irq;
+	}
+
+	irq_data = irq_get_irq_data(irq);
+	if (!irq_data) {
+		dev_err(dev, "irq_get_irq_data(%d): NULL\n", irq);
+		return -EINVAL;
+	}
+
+	bootreg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!bootreg_res) {
+		dev_err(dev,
+			"platform_get_resource(IORESOURCE_MEM, 0): NULL\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	chipsig_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (!chipsig_res) {
+		dev_err(dev,
+			"platform_get_resource(IORESOURCE_MEM, 1): NULL\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	bootreg = devm_request_and_ioremap(dev, bootreg_res);
+	if (!bootreg) {
+		dev_err(dev, "unable to map boot register\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	chipsig = devm_request_and_ioremap(dev, chipsig_res);
+	if (!chipsig) {
+		dev_err(dev, "unable to map CHIPSIG register\n");
+		return -EADDRNOTAVAIL;
+	}
+
+	dsp_clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(dsp_clk)) {
+		dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk));
+
+		return PTR_RET(dsp_clk);
+	}
+
+	rproc = rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name,
+		sizeof(*drproc));
+	if (!rproc)
+		return -ENOMEM;
+
+	drproc = rproc->priv;
+	drproc->rproc = rproc;
+
+	platform_set_drvdata(pdev, rproc);
+
+	/* everything the ISR needs is now setup, so hook it up */
+	ret = devm_request_threaded_irq(dev, irq, da8xx_rproc_callback,
+		handle_event, 0, "da8xx-remoteproc", rproc);
+	if (ret) {
+		dev_err(dev, "devm_request_threaded_irq error: %d\n", ret);
+		goto free_rproc;
+	}
+
+	/*
+	 * rproc_add() can end up enabling the DSP's clk with the DSP
+	 * *not* in reset, but da8xx_rproc_start() needs the DSP to be
+	 * held in reset at the time it is called.
+	 */
+	ret = reset_assert(dev);
+	if (ret)
+		goto free_rproc;
+
+	ret = rproc_add(rproc);
+	if (ret) {
+		dev_err(dev, "rproc_add failed: %d\n", ret);
+		goto free_rproc;
+	}
+
+	drproc->chipsig = chipsig;
+	drproc->bootreg = bootreg;
+	drproc->ack_fxn = irq_data->chip->irq_ack;
+	drproc->irq_data = irq_data;
+	drproc->irq = irq;
+	drproc->dsp_clk = dsp_clk;
+
+	return 0;
+
+free_rproc:
+	rproc_put(rproc);
+
+	return ret;
+}
+
+static int da8xx_rproc_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rproc *rproc = platform_get_drvdata(pdev);
+	struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
+
+	/*
+	 * It's important to place the DSP in reset before going away,
+	 * since a subsequent insmod of this module may enable the DSP's
+	 * clock before its program/boot-address has been loaded and
+	 * before this module's probe has had a chance to reset the DSP.
+	 * Without the reset, the DSP can lockup permanently when it
+	 * begins executing garbage.
+	 */
+	reset_assert(dev);
+
+	/*
+	 * The devm subsystem might end up releasing things before
+	 * freeing the irq, thus allowing an interrupt to sneak in while
+	 * the device is being removed.  This should prevent that.
+	 */
+	disable_irq(drproc->irq);
+
+	devm_clk_put(dev, drproc->dsp_clk);
+
+	rproc_del(rproc);
+	rproc_put(rproc);
+
+	return 0;
+}
+
+static struct platform_driver da8xx_rproc_driver = {
+	.probe = da8xx_rproc_probe,
+	.remove = da8xx_rproc_remove,
+	.driver = {
+		.name = "davinci-rproc",
+		.owner = THIS_MODULE,
+	},
+};
+
+module_platform_driver(da8xx_rproc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("DA8XX Remote Processor control driver");