summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Agner <stefan.agner@toradex.com>2016-08-24 11:25:37 -0700
committerStefan Agner <stefan.agner@toradex.com>2016-09-29 13:57:42 -0700
commit8f2dbc30ed0274c960b9a29086d677e111551e5a (patch)
tree38e9c0229b119f2324ebc5e26ab91d8d793fd317
parente11f8ba48c0767f787067a382ddda707109d9305 (diff)
parent8439c80d3611af210ce03fbc9d4e4571b340b0fe (diff)
Merge branch 'toradex_vf_4.4-rpmsg' into toradex_vf_4.4-next
-rw-r--r--arch/arm/boot/dts/vf610.dtsi27
-rw-r--r--arch/arm/boot/dts/vfxxx.dtsi40
-rw-r--r--arch/arm/configs/colibri_vf_defconfig6
-rw-r--r--drivers/char/Kconfig6
-rw-r--r--drivers/char/Makefile1
-rw-r--r--drivers/char/vf610_sema4.c322
-rw-r--r--drivers/irqchip/irq-vf610-mscm-ir.c126
-rw-r--r--drivers/remoteproc/Kconfig9
-rw-r--r--drivers/remoteproc/Makefile1
-rw-r--r--drivers/remoteproc/remoteproc_core.c48
-rw-r--r--drivers/remoteproc/vf610_cm4_rproc.c259
-rw-r--r--drivers/rpmsg/Kconfig12
-rw-r--r--drivers/rpmsg/Makefile3
-rw-r--r--drivers/rpmsg/imx_rpmsg_pingpong.c110
-rw-r--r--drivers/rpmsg/imx_rpmsg_tty.c233
-rw-r--r--drivers/rpmsg/vf610_rpmsg.c353
-rw-r--r--include/linux/vf610_mscm.h13
-rw-r--r--include/linux/vf610_sema4.h83
18 files changed, 1594 insertions, 58 deletions
diff --git a/arch/arm/boot/dts/vf610.dtsi b/arch/arm/boot/dts/vf610.dtsi
index 58bc6e448be5..f731b598e2c5 100644
--- a/arch/arm/boot/dts/vf610.dtsi
+++ b/arch/arm/boot/dts/vf610.dtsi
@@ -22,4 +22,31 @@
arm,data-latency = <3 3 3>;
arm,tag-latency = <2 2 2>;
};
+
+ sema4: sema4@4001D000 {
+ compatible = "fsl,vf610-sema4";
+ reg = <0x4001D000 0x1000>;
+ interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ CM4: cortexm4 {
+ compatible = "simple-bus";
+ ranges = <0x1f000000 0x3f000000 0x80000
+ 0x3f000000 0x3f000000 0x80000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ cortexm4core {
+ compatible = "fsl,vf610-m4";
+ reg = <0x1f000000 0x80000>,
+ <0x3f000000 0x80000>;
+ reg-names = "pc_ocram", "ps_ocram";
+ fsl,firmware = "freertos-rpmsg.elf";
+ };
+ };
+
+ rpmsg: rpmsg {
+ compatible = "fsl,vf610-rpmsg";
+ status = "okay";
+ };
};
diff --git a/arch/arm/boot/dts/vfxxx.dtsi b/arch/arm/boot/dts/vfxxx.dtsi
index 3c64f11fbcd8..a0d5255683b6 100644
--- a/arch/arm/boot/dts/vfxxx.dtsi
+++ b/arch/arm/boot/dts/vfxxx.dtsi
@@ -69,32 +69,6 @@
reg = <0x00000000 0x18000>;
};
- ocram0: sram@3f000000 {
- compatible = "mmio-sram";
- reg = <0x3f000000 0x40000>;
-
- #address-cells = <1>;
- #size-cells = <1>;
- ranges = <0 0x3f000000 0x40000>;
-
- stbyram1@0 {
- reg = <0x0 0x4000>;
- label = "stbyram1";
- pool;
- };
-
- stbyram2@4000 {
- reg = <0x4000 0xc000>;
- label = "stbyram2";
- pool;
- };
- };
-
- ocram1: sram@3f040000 {
- compatible = "mmio-sram";
- reg = <0x3f040000 0x40000>;
- };
-
gfxram0: sram@3f400000 {
compatible = "mmio-sram";
reg = <0x3f400000 0x80000>;
@@ -119,10 +93,22 @@
mscm_ir: interrupt-controller@40001800 {
compatible = "fsl,vf610-mscm-ir";
+ #address-cells = <1>;
+ #size-cells = <1>;
reg = <0x40001800 0x400>;
fsl,cpucfg = <&mscm_cpucfg>;
interrupt-controller;
#interrupt-cells = <2>;
+
+ cpu2cpu@40001800 {
+ reg = <0x40001800 0x40>;
+ interrupt-parent = <&mscm_ir>;
+ interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
+ <1 IRQ_TYPE_LEVEL_HIGH>,
+ <2 IRQ_TYPE_LEVEL_HIGH>,
+ <3 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "int0", "int1", "int2", "int3";
+ };
};
edma0: dma-controller@40018000 {
@@ -498,7 +484,7 @@
};
clks: ccm@4006b000 {
- compatible = "fsl,vf610-ccm";
+ compatible = "fsl,vf610-ccm", "syscon";
reg = <0x4006b000 0x1000>;
clocks = <&sxosc>, <&fxosc>;
clock-names = "sxosc", "fxosc";
diff --git a/arch/arm/configs/colibri_vf_defconfig b/arch/arm/configs/colibri_vf_defconfig
index f23d1c5ded42..df1e04ef7358 100644
--- a/arch/arm/configs/colibri_vf_defconfig
+++ b/arch/arm/configs/colibri_vf_defconfig
@@ -321,3 +321,9 @@ CONFIG_XZ_DEC=y
CONFIG_FONTS=y
CONFIG_FONT_8x8=y
CONFIG_FONT_8x16=y
+CONFIG_VF610_SEMA4=y
+CONFIG_VIRTIO=m
+CONFIG_VF610_CM4_RPROC=m
+CONFIG_IMX_RPMSG_PINGPONG=m
+CONFIG_IMX_RPMSG_TTY=m
+CONFIG_VF610_RPMSG=m
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index a043107da2af..1654733cf429 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -24,6 +24,12 @@ config DEVKMEM
kind of kernel debugging operations.
When in doubt, say "N".
+config VF610_SEMA4
+ bool "VF610 SEMA4 driver"
+ depends on SOC_VF610
+ help
+ Support for VF610 SEMA4 driver, most people should say N here.
+
config SGI_SNSC
bool "SGI Altix system controller communication support"
depends on (IA64_SGI_SN2 || IA64_GENERIC)
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index d8a7579300d2..fef56bfad9db 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -59,4 +59,5 @@ obj-$(CONFIG_JS_RTC) += js-rtc.o
js-rtc-y = rtc.o
obj-$(CONFIG_TILE_SROM) += tile-srom.o
+obj-$(CONFIG_VF610_SEMA4) += vf610_sema4.o
obj-$(CONFIG_XILLYBUS) += xillybus/
diff --git a/drivers/char/vf610_sema4.c b/drivers/char/vf610_sema4.c
new file mode 100644
index 000000000000..352676a307ca
--- /dev/null
+++ b/drivers/char/vf610_sema4.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2016 Toradex AG.
+ *
+ * Taken from the 4.1.15 kernel release for i.MX7 by Freescale.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/vf610_sema4.h>
+
+static struct vf610_sema4_mutex_device *vf610_sema4;
+
+struct vf610_sema4_mutex *
+vf610_sema4_mutex_create(u32 dev_num, u32 mutex_num)
+{
+ struct vf610_sema4_mutex *mutex_ptr = NULL;
+
+ if (mutex_num >= SEMA4_NUM_GATES || dev_num >= SEMA4_NUM_DEVICES)
+ goto out;
+
+ if (vf610_sema4->cpine_val & (1 < mutex_num)) {
+ pr_err("Error: requiring a allocated sema4.\n");
+ pr_err("mutex_num %d cpine_val 0x%08x.\n",
+ mutex_num, vf610_sema4->cpine_val);
+ }
+ mutex_ptr = kzalloc(sizeof(*mutex_ptr), GFP_KERNEL);
+ if (!mutex_ptr)
+ goto out;
+ vf610_sema4->mutex_ptr[mutex_num] = mutex_ptr;
+ vf610_sema4->alloced |= 1 < mutex_num;
+ vf610_sema4->cpine_val |= idx_sema4[mutex_num];
+ writew(vf610_sema4->cpine_val, vf610_sema4->ioaddr + SEMA4_CP0INE);
+
+ mutex_ptr->valid = CORE_MUTEX_VALID;
+ mutex_ptr->gate_num = mutex_num;
+ init_waitqueue_head(&mutex_ptr->wait_q);
+
+out:
+ return mutex_ptr;
+}
+EXPORT_SYMBOL(vf610_sema4_mutex_create);
+
+int vf610_sema4_mutex_destroy(struct vf610_sema4_mutex *mutex_ptr)
+{
+ u32 mutex_num;
+
+ if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID))
+ return -EINVAL;
+
+ mutex_num = mutex_ptr->gate_num;
+ if ((vf610_sema4->cpine_val & idx_sema4[mutex_num]) == 0) {
+ pr_err("Error: trying to destroy a un-allocated sema4.\n");
+ pr_err("mutex_num %d cpine_val 0x%08x.\n",
+ mutex_num, vf610_sema4->cpine_val);
+ }
+ vf610_sema4->mutex_ptr[mutex_num] = NULL;
+ vf610_sema4->alloced &= ~(1 << mutex_num);
+ vf610_sema4->cpine_val &= ~(idx_sema4[mutex_num]);
+ writew(vf610_sema4->cpine_val, vf610_sema4->ioaddr + SEMA4_CP0INE);
+
+ kfree(mutex_ptr);
+
+ return 0;
+}
+EXPORT_SYMBOL(vf610_sema4_mutex_destroy);
+
+int _vf610_sema4_mutex_lock(struct vf610_sema4_mutex *mutex_ptr)
+{
+ int ret = 0, i = 0;
+
+ if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID))
+ return -EINVAL;
+
+ i = mutex_ptr->gate_num;
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* Check to see if this core already own it */
+ if (mutex_ptr->gate_val == SEMA4_A5_LOCK) {
+ /* return -EBUSY, invoker should be in sleep, and re-lock ag */
+ pr_err("%s -> %s %d already locked, wait! num %d val %d.\n",
+ __FILE__, __func__, __LINE__,
+ i, mutex_ptr->gate_val);
+ ret = -EBUSY;
+ goto out;
+ } else {
+ /* try to lock the mutex */
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ mutex_ptr->gate_val |= SEMA4_A5_LOCK;
+ writeb(mutex_ptr->gate_val, vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* double check the mutex is locked, otherwise, return -EBUSY */
+ if (mutex_ptr->gate_val != SEMA4_A5_LOCK) {
+ pr_debug("wait-locked num %d val %d.\n",
+ i, mutex_ptr->gate_val);
+ ret = -EBUSY;
+ }
+ }
+out:
+ return ret;
+}
+
+int vf610_sema4_mutex_trylock(struct vf610_sema4_mutex *mutex_ptr)
+{
+ int ret = 0;
+
+ ret = _vf610_sema4_mutex_lock(mutex_ptr);
+ if (ret == 0)
+ return SEMA4_A5_LOCK;
+ else
+ return ret;
+}
+EXPORT_SYMBOL(vf610_sema4_mutex_trylock);
+
+int vf610_sema4_mutex_lock(struct vf610_sema4_mutex *mutex_ptr)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vf610_sema4->lock, flags);
+ ret = _vf610_sema4_mutex_lock(mutex_ptr);
+ spin_unlock_irqrestore(&vf610_sema4->lock, flags);
+ while (-EBUSY == ret) {
+ spin_lock_irqsave(&vf610_sema4->lock, flags);
+ ret = _vf610_sema4_mutex_lock(mutex_ptr);
+ spin_unlock_irqrestore(&vf610_sema4->lock, flags);
+ if (ret == 0)
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(vf610_sema4_mutex_lock);
+
+int vf610_sema4_mutex_unlock(struct vf610_sema4_mutex *mutex_ptr)
+{
+ int ret = 0, i = 0;
+
+ if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID))
+ return -EINVAL;
+
+ i = mutex_ptr->gate_num;
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* make sure it is locked by this core */
+ if (mutex_ptr->gate_val != SEMA4_A5_LOCK) {
+ pr_err("%d Trying to unlock an unlock mutex.\n", __LINE__);
+ ret = -EINVAL;
+ goto out;
+ }
+ /* unlock it */
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ writeb(mutex_ptr->gate_val | SEMA4_UNLOCK, vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* make sure it is locked by this core */
+ if (mutex_ptr->gate_val == SEMA4_A5_LOCK)
+ pr_err("%d ERROR, failed to unlock the mutex.\n", __LINE__);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL(vf610_sema4_mutex_unlock);
+
+static irqreturn_t vf610_sema4_isr(int irq, void *dev_id)
+{
+ int i;
+ struct vf610_sema4_mutex *mutex_ptr;
+ unsigned int mask;
+ struct vf610_sema4_mutex_device *vf610_sema4 = dev_id;
+
+ vf610_sema4->cpntf_val = readw(vf610_sema4->ioaddr + SEMA4_CP0NTF);
+ for (i = 0; i < SEMA4_NUM_GATES; i++) {
+ mask = idx_sema4[i];
+ if ((vf610_sema4->cpntf_val) & mask) {
+ mutex_ptr = vf610_sema4->mutex_ptr[i];
+ /*
+ * An interrupt is pending on this mutex, the only way
+ * to clear it is to lock it (either by this core or
+ * another).
+ */
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ mutex_ptr->gate_val |= SEMA4_A5_LOCK;
+ writeb(mutex_ptr->gate_val, vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val = readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ if (mutex_ptr->gate_val == SEMA4_A5_LOCK) {
+ /*
+ * wake up the wait queue, whatever there
+ * are wait task or not.
+ * NOTE: check gate is locted or not in
+ * sema4_lock func by wait task.
+ */
+ mutex_ptr->gate_val =
+ readb(vf610_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ mutex_ptr->gate_val |= SEMA4_UNLOCK;
+
+ writeb(mutex_ptr->gate_val,
+ vf610_sema4->ioaddr + i);
+ wake_up(&mutex_ptr->wait_q);
+ } else {
+ pr_debug("can't lock gate%d %s retry!\n", i,
+ mutex_ptr->gate_val ?
+ "locked by m4" : "");
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const struct of_device_id vf610_sema4_dt_ids[] = {
+ { .compatible = "fsl,vf610-sema4", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vf610_sema4_dt_ids);
+
+static int vf610_sema4_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret;
+
+ vf610_sema4 = devm_kzalloc(&pdev->dev, sizeof(*vf610_sema4), GFP_KERNEL);
+ if (!vf610_sema4)
+ return -ENOMEM;
+
+ vf610_sema4->dev = &pdev->dev;
+ vf610_sema4->cpine_val = 0;
+ spin_lock_init(&vf610_sema4->lock);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR(res)) {
+ dev_err(&pdev->dev, "unable to get vf610 sema4 resource 0\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ vf610_sema4->ioaddr = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(vf610_sema4->ioaddr)) {
+ ret = PTR_ERR(vf610_sema4->ioaddr);
+ goto err;
+ }
+
+ vf610_sema4->irq = platform_get_irq(pdev, 0);
+ if (!vf610_sema4->irq) {
+ dev_err(&pdev->dev, "failed to get irq\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ret = devm_request_irq(&pdev->dev, vf610_sema4->irq, vf610_sema4_isr,
+ IRQF_SHARED, "vf610-sema4", vf610_sema4);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request vf610 sema4 irq\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, vf610_sema4);
+
+err:
+ return ret;
+}
+
+static int vf610_sema4_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver vf610_sema4_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "vf610-sema4",
+ .of_match_table = vf610_sema4_dt_ids,
+ },
+ .probe = vf610_sema4_probe,
+ .remove = vf610_sema4_remove,
+};
+
+static int __init vf610_sema4_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&vf610_sema4_driver);
+ if (ret)
+ pr_err("Unable to initialize sema4 driver\n");
+ else
+ pr_info("vf610 sema4 driver is registered.\n");
+
+ return ret;
+}
+
+static void __exit vf610_sema4_exit(void)
+{
+ pr_info("vf610 sema4 driver is unregistered.\n");
+ platform_driver_unregister(&vf610_sema4_driver);
+}
+
+module_exit(vf610_sema4_exit);
+module_init(vf610_sema4_init);
+
+MODULE_DESCRIPTION("VF610 SEMA4 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/irqchip/irq-vf610-mscm-ir.c b/drivers/irqchip/irq-vf610-mscm-ir.c
index 56b5e3cb9de2..a36f2ea74185 100644
--- a/drivers/irqchip/irq-vf610-mscm-ir.c
+++ b/drivers/irqchip/irq-vf610-mscm-ir.c
@@ -23,8 +23,10 @@
* variants of Vybrid.
*/
+#include <linux/bitops.h>
#include <linux/cpu_pm.h>
#include <linux/io.h>
+#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
@@ -32,25 +34,48 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#define MSCM_CPxNUM 0x4
+#define MSCM_IRCP0IR 0x0
+#define MSCM_IRCP1IR 0x4
+#define MSCM_IRCPnIR(n) ((n) * 0x4 + MSCM_IRCP0IR)
+#define MSCM_IRCPnIR_INT(n) (0x1 << (n))
+#define MSCM_IRCPGIR 0x20
+
+#define MSCM_INTID_MASK 0x3
+#define MSCM_INTID(n) ((n) & MSCM_INTID_MASK)
+#define MSCM_CPUTL(n) (((n) == 0 ? 1 : 2) << 16)
+
#define MSCM_IRSPRC(n) (0x80 + 2 * (n))
#define MSCM_IRSPRC_CPEN_MASK 0x3
#define MSCM_IRSPRC_NUM 112
+#define MSCM_CPU2CPU_NUM 4
+
struct vf610_mscm_ir_chip_data {
void __iomem *mscm_ir_base;
- u16 cpu_mask;
+ u16 cpu_id;
u16 saved_irsprc[MSCM_IRSPRC_NUM];
bool is_nvic;
+ struct device_node *cpu2cpu_node;
+};
+
+struct mscm_cpu2cpu_irq_data {
+ int intid;
+ int irq;
+ irq_handler_t handler;
+ void *priv;
};
static struct vf610_mscm_ir_chip_data *mscm_ir_data;
+static struct mscm_cpu2cpu_irq_data cpu2cpu_irq_data[MSCM_CPU2CPU_NUM];
+
static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data)
{
int i;
@@ -94,11 +119,8 @@ static void vf610_mscm_ir_enable(struct irq_data *data)
u16 irsprc;
irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
- irsprc &= MSCM_IRSPRC_CPEN_MASK;
- WARN_ON(irsprc & ~chip_data->cpu_mask);
-
- writew_relaxed(chip_data->cpu_mask,
+ writew_relaxed(irsprc | BIT(chip_data->cpu_id),
chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
irq_chip_enable_parent(data);
@@ -108,8 +130,11 @@ static void vf610_mscm_ir_disable(struct irq_data *data)
{
irq_hw_number_t hwirq = data->hwirq;
struct vf610_mscm_ir_chip_data *chip_data = data->chip_data;
+ u16 irsprc;
- writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
+ irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
+ writew_relaxed(irsprc & ~BIT(chip_data->cpu_id),
+ chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
irq_chip_disable_parent(data);
}
@@ -179,6 +204,90 @@ static const struct irq_domain_ops mscm_irq_domain_ops = {
.free = irq_domain_free_irqs_common,
};
+static irqreturn_t mscm_cpu2cpu_irq_handler(int irq, void *dev_id)
+{
+ irqreturn_t ret;
+ struct mscm_cpu2cpu_irq_data *data = dev_id;
+ void __iomem *mscm_base = mscm_ir_data->mscm_ir_base;
+ int cpu_id = mscm_ir_data->cpu_id;
+
+
+ ret = data->handler(data->intid, data->priv);
+ if (ret == IRQ_HANDLED)
+ writel(MSCM_IRCPnIR_INT(data->intid), mscm_base + MSCM_IRCPnIR(cpu_id));
+
+ return ret;
+}
+
+int mscm_request_cpu2cpu_irq(unsigned int intid, irq_handler_t handler,
+ const char *name, void *priv)
+{
+ int irq;
+ struct mscm_cpu2cpu_irq_data *data;
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return -EINVAL;
+
+ irq = of_irq_get(mscm_ir_data->cpu2cpu_node, intid);
+ if (irq < 0)
+ return irq;
+
+ data = &cpu2cpu_irq_data[intid];
+ data->intid = intid;
+ data->irq = irq;
+ data->handler = handler;
+ data->priv = priv;
+
+ return request_irq(irq, mscm_cpu2cpu_irq_handler, 0, name, data);
+}
+EXPORT_SYMBOL(mscm_request_cpu2cpu_irq);
+
+void mscm_free_cpu2cpu_irq(unsigned int intid, void *priv)
+{
+ struct mscm_cpu2cpu_irq_data *data;
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ data = &cpu2cpu_irq_data[intid];
+
+ if (data->irq < 0)
+ return;
+
+ free_irq(data->irq, data);
+}
+EXPORT_SYMBOL(mscm_free_cpu2cpu_irq);
+
+void mscm_trigger_cpu2cpu_irq(unsigned int intid, int cpuid)
+{
+ void __iomem *mscm_base = mscm_ir_data->mscm_ir_base;
+
+ writel(MSCM_INTID(intid) | MSCM_CPUTL(cpuid), mscm_base + MSCM_IRCPGIR);
+}
+EXPORT_SYMBOL(mscm_trigger_cpu2cpu_irq);
+
+void mscm_enable_cpu2cpu_irq(unsigned int intid)
+{
+ struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid];
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ enable_irq(data->irq);
+}
+EXPORT_SYMBOL(mscm_enable_cpu2cpu_irq);
+
+void mscm_disable_cpu2cpu_irq(unsigned int intid)
+{
+ struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid];
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ disable_irq(data->irq);
+}
+EXPORT_SYMBOL(mscm_disable_cpu2cpu_irq);
+
static int __init vf610_mscm_ir_of_init(struct device_node *node,
struct device_node *parent)
{
@@ -210,8 +319,7 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node,
goto out_unmap;
}
- regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid);
- mscm_ir_data->cpu_mask = 0x1 << cpuid;
+ mscm_ir_data->cpu_id = regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid);
domain = irq_domain_add_hierarchy(domain_parent, 0,
MSCM_IRSPRC_NUM, node,
@@ -227,6 +335,8 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node,
cpu_pm_register_notifier(&mscm_ir_notifier_block);
+ mscm_ir_data->cpu2cpu_node = of_get_child_by_name(node, "cpu2cpu");
+
return 0;
out_unmap:
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 28c711f0ac6b..1dd53b1ebf6b 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -41,6 +41,15 @@ config STE_MODEM_RPROC
This can be either built-in or a loadable module.
If unsure say N.
+config VF610_CM4_RPROC
+ tristate "Freescale Vybrid Cortex-M4 remoteproc support"
+ depends on SOC_VF610
+ select REMOTEPROC
+ select RPMSG
+ help
+ Say y here to support Freescale Vybrid's secondary core via the
+ remote processor framework.
+
config WKUP_M3_RPROC
tristate "AMx3xx Wakeup M3 remoteproc support"
depends on SOC_AM33XX || SOC_AM43XX
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 81b04d1e2e58..df1814ff283c 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -9,5 +9,6 @@ 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_VF610_CM4_RPROC) += vf610_cm4_rproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index 9e03d158f411..4877c8414f56 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -799,12 +799,11 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
struct resource_table *table, *loaded_table;
int ret, tablesz;
- if (!rproc->table_ptr)
- return -ENOMEM;
-
ret = rproc_fw_sanity_check(rproc, fw);
- if (ret)
+ if (ret) {
+ dev_err(dev, "rproc_fw_sanity_check returned %d\n", ret);
return ret;
+ }
dev_info(dev, "Booting fw image %s, size %zd\n", name, fw->size);
@@ -823,20 +822,22 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
/* look for the resource table */
table = rproc_find_rsc_table(rproc, fw, &tablesz);
- if (!table)
- goto clean_up;
-
- /* Verify that resource table in loaded fw is unchanged */
- if (rproc->table_csum != crc32(0, table, tablesz)) {
- dev_err(dev, "resource checksum failed, fw changed?\n");
- goto clean_up;
- }
+ if (!table) {
+ dev_info(dev, "No resource table found, continuing...\n");
+ } else {
+ /* Verify that resource table in loaded fw is unchanged */
+ if (rproc->table_csum != crc32(0, table, tablesz)) {
+ dev_err(dev, "resource checksum failed, fw changed?\n");
+ goto clean_up;
+ }
- /* handle fw resources which are required to boot rproc */
- ret = rproc_handle_resources(rproc, tablesz, rproc_loading_handlers);
- if (ret) {
- dev_err(dev, "Failed to process resources: %d\n", ret);
- goto clean_up;
+ /* handle fw resources which are required to boot rproc */
+ ret = rproc_handle_resources(rproc, tablesz,
+ rproc_loading_handlers);
+ if (ret) {
+ dev_err(dev, "Failed to process resources: %d\n", ret);
+ goto clean_up;
+ }
}
/* load the ELF segments to memory */
@@ -853,14 +854,15 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
* In order to pass this information to the remote device we must
* copy this information to device memory.
*/
- loaded_table = rproc_find_loaded_rsc_table(rproc, fw);
- if (!loaded_table) {
- ret = -EINVAL;
- goto clean_up;
+ if (table) {
+ loaded_table = rproc_find_loaded_rsc_table(rproc, fw);
+ if (!loaded_table) {
+ ret = -EINVAL;
+ goto clean_up;
+ }
+ memcpy(loaded_table, rproc->cached_table, tablesz);
}
- memcpy(loaded_table, rproc->cached_table, tablesz);
-
/* power up the remote processor */
ret = rproc->ops->start(rproc);
if (ret) {
diff --git a/drivers/remoteproc/vf610_cm4_rproc.c b/drivers/remoteproc/vf610_cm4_rproc.c
new file mode 100644
index 000000000000..da45162abc12
--- /dev/null
+++ b/drivers/remoteproc/vf610_cm4_rproc.c
@@ -0,0 +1,259 @@
+/*
+ * Freescale VF610 Cortex-M4 Remote Processor driver
+ *
+ * Copyright (C) 2016 Toradex AG
+ *
+ * Based on wkup_m3_rproc.c by:
+ * Copyright (C) 2014-2015 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+
+#include "remoteproc_internal.h"
+
+#define CM4_MEM_MAX 2
+
+/*
+ * struct vf610_m4_mem - Cm4 internal memory structure
+ * @cpu_addr: MPU virtual address of the memory region
+ * @bus_addr: Bus address used to access the memory region
+ * @dev_addr: Device address from Wakeup M4 view
+ * @size: Size of the memory region
+ */
+struct vf610_m4_mem {
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ u32 dev_addr;
+ size_t size;
+};
+
+/*
+ * struct vf610_m4_rproc - Cm4 remote processor state
+ * @rproc: rproc handle
+ * @pdev: pointer to platform device
+ * @mem: Cm4 memory information
+ */
+struct vf610_m4_rproc {
+ struct rproc *rproc;
+ struct platform_device *pdev;
+ struct vf610_m4_mem mem[CM4_MEM_MAX];
+ struct regmap *ccm;
+ struct regmap *src;
+};
+
+static int vf610_m4_rproc_start(struct rproc *rproc)
+{
+ struct vf610_m4_rproc *cm4 = rproc->priv;
+
+ regmap_write(cm4->src, 0x28, rproc->bootaddr);
+ regmap_write(cm4->ccm, 0x8c, 0x00015a5a);
+
+ return 0;
+}
+
+static int vf610_m4_rproc_stop(struct rproc *rproc)
+{
+ return -EINVAL;
+}
+
+static void *vf610_m4_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
+{
+ struct vf610_m4_rproc *cm4 = rproc->priv;
+ void *va = NULL;
+ u32 offset;
+ int i;
+
+ if (len <= 0)
+ return NULL;
+
+ for (i = 0; i < CM4_MEM_MAX; i++) {
+ if (da >= cm4->mem[i].dev_addr && da + len <=
+ cm4->mem[i].dev_addr + cm4->mem[i].size) {
+ offset = da - cm4->mem[i].dev_addr;
+ /* __force to make sparse happy with type conversion */
+ va = (__force void *)(cm4->mem[i].cpu_addr + offset);
+ break;
+ }
+ }
+
+ return va;
+}
+
+static void vf610_m4_rproc_kick(struct rproc *rproc, int vqid)
+{
+ /*
+ * We provide this just to prevent Linux from complaining
+ * and causing a kernel panic.
+ */
+}
+
+static struct rproc_ops vf610_m4_rproc_ops = {
+ .start = vf610_m4_rproc_start,
+ .stop = vf610_m4_rproc_stop,
+ .da_to_va = vf610_m4_rproc_da_to_va,
+ .kick = vf610_m4_rproc_kick,
+};
+
+static const struct of_device_id vf610_m4_rproc_of_match[] = {
+ { .compatible = "fsl,vf610-m4", },
+ {},
+};
+
+static int vf610_m4_rproc_probe(struct platform_device *pdev)
+{
+ const char *mem_names[CM4_MEM_MAX] = { "pc_ocram", "ps_ocram" };
+ struct device *dev = &pdev->dev;
+ struct vf610_m4_rproc *cm4;
+ const char *fw_name;
+ struct rproc *rproc;
+ struct resource *res;
+ const __be32 *addrp;
+ u32 l4_offset = 0;
+ u64 size;
+ int ret;
+ int i, j;
+
+ ret = of_property_read_string(dev->of_node, "fsl,firmware",
+ &fw_name);
+ if (ret) {
+ dev_err(dev, "No firmware filename given\n");
+ return -ENODEV;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "pm_runtime_get_sync() failed\n");
+ goto err;
+ }
+
+ rproc = rproc_alloc(dev, "vf610_m4", &vf610_m4_rproc_ops,
+ fw_name, sizeof(*cm4));
+ if (!rproc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ cm4 = rproc->priv;
+ cm4->rproc = rproc;
+ cm4->pdev = pdev;
+
+ cm4->src = syscon_regmap_lookup_by_compatible("fsl,vf610-src");
+ cm4->ccm = syscon_regmap_lookup_by_compatible("fsl,vf610-ccm");
+
+ for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ mem_names[i]);
+
+ /*
+ * The Cortex-M4 has address aliases, which means we
+ * have two device addresses which map to the same
+ * bus address. From Linux side, just use the already
+ * mapped region for those cases.
+ */
+ for (j = i - 1; j >= 0; j--) {
+ if (res->start == cm4->mem[j].bus_addr) {
+ cm4->mem[i].cpu_addr =
+ cm4->mem[j].cpu_addr;
+ break;
+ }
+ }
+
+ /* No previous mapping found, create a new mapping */
+ if (j < 0) {
+ cm4->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
+ if (IS_ERR(cm4->mem[i].cpu_addr)) {
+ dev_err(&pdev->dev,
+ "devm_ioremap_resource failed for resource %d\n", i);
+ ret = PTR_ERR(cm4->mem[i].cpu_addr);
+ goto err;
+ }
+ }
+
+ cm4->mem[i].bus_addr = res->start;
+ cm4->mem[i].size = resource_size(res);
+ addrp = of_get_address(dev->of_node, i, &size, NULL);
+ cm4->mem[i].dev_addr = be32_to_cpu(*addrp) - l4_offset;
+ }
+
+ dev_set_drvdata(dev, rproc);
+
+ ret = rproc_add(rproc);
+ if (ret) {
+ dev_err(dev, "rproc_add failed\n");
+ goto err_put_rproc;
+ }
+
+ rproc_boot(rproc);
+
+ return 0;
+
+err_put_rproc:
+ rproc_put(rproc);
+err:
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static int vf610_m4_rproc_remove(struct platform_device *pdev)
+{
+ struct rproc *rproc = platform_get_drvdata(pdev);
+
+ rproc_del(rproc);
+ rproc_put(rproc);
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int vf610_m4_rpm_suspend(struct device *dev)
+{
+ return -EBUSY;
+}
+
+static int vf610_m4_rpm_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops vf610_m4_rproc_pm_ops = {
+ SET_RUNTIME_PM_OPS(vf610_m4_rpm_suspend, vf610_m4_rpm_resume, NULL)
+};
+
+static struct platform_driver vf610_m4_rproc_driver = {
+ .probe = vf610_m4_rproc_probe,
+ .remove = vf610_m4_rproc_remove,
+ .driver = {
+ .name = "vf610_m4_rproc",
+ .of_match_table = vf610_m4_rproc_of_match,
+ .pm = &vf610_m4_rproc_pm_ops,
+ },
+};
+module_platform_driver(vf610_m4_rproc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Freescale Vybrid Cortex-M4 Remote Processor driver");
+MODULE_AUTHOR("Stefan Agner <stefan.agner@toradex.com>");
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
index 69a219387582..4f42214bf2d6 100644
--- a/drivers/rpmsg/Kconfig
+++ b/drivers/rpmsg/Kconfig
@@ -6,4 +6,16 @@ config RPMSG
select VIRTIO
select VIRTUALIZATION
+config IMX_RPMSG_PINGPONG
+ tristate "IMX RPMSG pingpong driver -- loadable modules only"
+ depends on RPMSG && m
+
+config IMX_RPMSG_TTY
+ tristate "IMX RPMSG tty driver -- loadable modules only"
+ depends on RPMSG && m
+
+config VF610_RPMSG
+ tristate "VF610 RPMSG driver -- loadable modules only"
+ depends on RPMSG && m
+
endmenu
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index 7617fcb8259f..c19b2f7b41c4 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -1 +1,4 @@
obj-$(CONFIG_RPMSG) += virtio_rpmsg_bus.o
+obj-$(CONFIG_IMX_RPMSG_PINGPONG) += imx_rpmsg_pingpong.o
+obj-$(CONFIG_IMX_RPMSG_TTY) += imx_rpmsg_tty.o
+obj-$(CONFIG_VF610_RPMSG) += vf610_rpmsg.o
diff --git a/drivers/rpmsg/imx_rpmsg_pingpong.c b/drivers/rpmsg/imx_rpmsg_pingpong.c
new file mode 100644
index 000000000000..a6843b360e80
--- /dev/null
+++ b/drivers/rpmsg/imx_rpmsg_pingpong.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 Freescale Semiconductor, Inc.
+ *
+ * derived from the omap-rpmsg implementation.
+ * Remote processor messaging transport - pingpong driver
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/virtio.h>
+#include <linux/rpmsg.h>
+
+#define MSG "hello world!"
+#define MSG_LIMIT 1000
+static unsigned int rpmsg_pingpong;
+static int rx_count;
+
+static void rpmsg_pingpong_cb(struct rpmsg_channel *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ int err;
+
+ /* reply */
+ rpmsg_pingpong = *(unsigned int *)data;
+ pr_info("get %d (src: 0x%x)\n",
+ rpmsg_pingpong, src);
+ rx_count++;
+
+ /* pingpongs should not live forever */
+ if (rx_count >= MSG_LIMIT) {
+ dev_info(&rpdev->dev, "goodbye!\n");
+ return;
+ }
+ rpmsg_pingpong++;
+ err = rpmsg_sendto(rpdev, (void *)(&rpmsg_pingpong), 4, src);
+
+ if (err)
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err);
+}
+
+static int rpmsg_pingpong_probe(struct rpmsg_channel *rpdev)
+{
+ int err;
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
+ rpdev->src, rpdev->dst);
+
+ /*
+ * send a message to our remote processor, and tell remote
+ * processor about this channel
+ */
+ err = rpmsg_send(rpdev, MSG, strlen(MSG));
+ if (err) {
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err);
+ return err;
+ }
+
+ rpmsg_pingpong = 0;
+ rx_count = 0;
+ err = rpmsg_sendto(rpdev, (void *)(&rpmsg_pingpong), 4, rpdev->dst);
+ if (err) {
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static void rpmsg_pingpong_remove(struct rpmsg_channel *rpdev)
+{
+ dev_info(&rpdev->dev, "rpmsg pingpong driver is removed\n");
+}
+
+static struct rpmsg_device_id rpmsg_driver_pingpong_id_table[] = {
+ { .name = "rpmsg-openamp-demo-channel" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_pingpong_id_table);
+
+static struct rpmsg_driver rpmsg_pingpong_driver = {
+ .drv.name = KBUILD_MODNAME,
+ .drv.owner = THIS_MODULE,
+ .id_table = rpmsg_driver_pingpong_id_table,
+ .probe = rpmsg_pingpong_probe,
+ .callback = rpmsg_pingpong_cb,
+ .remove = rpmsg_pingpong_remove,
+};
+
+static int __init init(void)
+{
+ return register_rpmsg_driver(&rpmsg_pingpong_driver);
+}
+
+static void __exit fini(void)
+{
+ unregister_rpmsg_driver(&rpmsg_pingpong_driver);
+}
+module_init(init);
+module_exit(fini);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("iMX virtio remote processor messaging pingpong driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/imx_rpmsg_tty.c b/drivers/rpmsg/imx_rpmsg_tty.c
new file mode 100644
index 000000000000..14904c7d517e
--- /dev/null
+++ b/drivers/rpmsg/imx_rpmsg_tty.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2015 Freescale Semiconductor, Inc.
+ *
+ * derived from the omap-rpmsg implementation.
+ * Remote processor messaging transport - tty driver
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/virtio.h>
+
+/*
+ * struct rpmsgtty_port - Wrapper struct for imx rpmsg tty port.
+ * @port: TTY port data
+ */
+struct rpmsgtty_port {
+ struct tty_port port;
+ spinlock_t rx_lock;
+ struct rpmsg_channel *rpdev;
+};
+
+static struct rpmsgtty_port rpmsg_tty_port;
+
+#define RPMSG_MAX_SIZE (512 - sizeof(struct rpmsg_hdr))
+#define MSG "hello world!"
+
+static void rpmsg_tty_cb(struct rpmsg_channel *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ int space;
+ unsigned char *cbuf;
+ struct rpmsgtty_port *cport = &rpmsg_tty_port;
+
+ /* flush the recv-ed none-zero data to tty node */
+ if (len == 0)
+ return;
+
+ dev_dbg(&rpdev->dev, "msg(<- src 0x%x) len %d\n", src, len);
+
+ print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1,
+ data, len, true);
+
+ spin_lock_bh(&cport->rx_lock);
+ space = tty_prepare_flip_string(&cport->port, &cbuf, len);
+ if (space <= 0) {
+ dev_err(&rpdev->dev, "No memory for tty_prepare_flip_string\n");
+ spin_unlock_bh(&cport->rx_lock);
+ return;
+ }
+
+ memcpy(cbuf, data, len);
+ tty_flip_buffer_push(&cport->port);
+ spin_unlock_bh(&cport->rx_lock);
+}
+
+static struct tty_port_operations rpmsgtty_port_ops = { };
+
+static int rpmsgtty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ return tty_port_install(&rpmsg_tty_port.port, driver, tty);
+}
+
+static int rpmsgtty_open(struct tty_struct *tty, struct file *filp)
+{
+ return tty_port_open(tty->port, tty, filp);
+}
+
+static void rpmsgtty_close(struct tty_struct *tty, struct file *filp)
+{
+ return tty_port_close(tty->port, tty, filp);
+}
+
+static int rpmsgtty_write(struct tty_struct *tty, const unsigned char *buf,
+ int total)
+{
+ int count, ret = 0;
+ const unsigned char *tbuf;
+ struct rpmsgtty_port *rptty_port = container_of(tty->port,
+ struct rpmsgtty_port, port);
+ struct rpmsg_channel *rpdev = rptty_port->rpdev;
+
+ if (NULL == buf) {
+ pr_err("buf shouldn't be null.\n");
+ return -ENOMEM;
+ }
+
+ count = total;
+ tbuf = buf;
+ do {
+ /* send a message to our remote processor */
+ ret = rpmsg_send(rpdev, (void *)tbuf,
+ count > RPMSG_MAX_SIZE ? RPMSG_MAX_SIZE : count);
+ if (ret) {
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+ return ret;
+ }
+
+ if (count > RPMSG_MAX_SIZE) {
+ count -= RPMSG_MAX_SIZE;
+ tbuf += RPMSG_MAX_SIZE;
+ } else {
+ count = 0;
+ }
+ } while (count > 0);
+
+ return total;
+}
+
+static int rpmsgtty_write_room(struct tty_struct *tty)
+{
+ /* report the space in the rpmsg buffer */
+ return RPMSG_MAX_SIZE;
+}
+
+static const struct tty_operations imxrpmsgtty_ops = {
+ .install = rpmsgtty_install,
+ .open = rpmsgtty_open,
+ .close = rpmsgtty_close,
+ .write = rpmsgtty_write,
+ .write_room = rpmsgtty_write_room,
+};
+
+static struct tty_driver *rpmsgtty_driver;
+
+static int rpmsg_tty_probe(struct rpmsg_channel *rpdev)
+{
+ int err;
+ struct rpmsgtty_port *cport = &rpmsg_tty_port;
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
+ rpdev->src, rpdev->dst);
+
+ /*
+ * send a message to our remote processor, and tell remote
+ * processor about this channel
+ */
+ err = rpmsg_send(rpdev, MSG, strlen(MSG));
+ if (err) {
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", err);
+ return err;
+ }
+
+ rpmsgtty_driver = tty_alloc_driver(1, TTY_DRIVER_UNNUMBERED_NODE);
+ if (IS_ERR(rpmsgtty_driver))
+ return PTR_ERR(rpmsgtty_driver);
+
+ rpmsgtty_driver->driver_name = "rpmsg_tty";
+ rpmsgtty_driver->name = "ttyRPMSG";
+ rpmsgtty_driver->major = TTYAUX_MAJOR;
+ rpmsgtty_driver->minor_start = 3;
+ rpmsgtty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+ rpmsgtty_driver->init_termios = tty_std_termios;
+
+ tty_set_operations(rpmsgtty_driver, &imxrpmsgtty_ops);
+
+ tty_port_init(&cport->port);
+ cport->port.ops = &rpmsgtty_port_ops;
+ spin_lock_init(&cport->rx_lock);
+ cport->port.low_latency = cport->port.flags | ASYNC_LOW_LATENCY;
+
+ err = tty_register_driver(rpmsgtty_driver);
+ if (err < 0) {
+ pr_err("Couldn't install rpmsg tty driver: err %d\n", err);
+ goto error;
+ } else
+ pr_info("Install rpmsg tty driver!\n");
+ cport->rpdev = rpdev;
+
+ return 0;
+
+error:
+ tty_unregister_driver(rpmsgtty_driver);
+ put_tty_driver(rpmsgtty_driver);
+ tty_port_destroy(&cport->port);
+ rpmsgtty_driver = NULL;
+
+ return err;
+}
+
+static void rpmsg_tty_remove(struct rpmsg_channel *rpdev)
+{
+ struct rpmsgtty_port *cport = &rpmsg_tty_port;
+
+ dev_info(&rpdev->dev, "rpmsg tty driver is removed\n");
+
+ tty_unregister_driver(rpmsgtty_driver);
+ put_tty_driver(rpmsgtty_driver);
+ tty_port_destroy(&cport->port);
+ rpmsgtty_driver = NULL;
+}
+
+static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
+ { .name = "rpmsg-openamp-demo-channel" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
+
+static struct rpmsg_driver rpmsg_tty_driver = {
+ .drv.name = KBUILD_MODNAME,
+ .drv.owner = THIS_MODULE,
+ .id_table = rpmsg_driver_tty_id_table,
+ .probe = rpmsg_tty_probe,
+ .callback = rpmsg_tty_cb,
+ .remove = rpmsg_tty_remove,
+};
+
+static int __init init(void)
+{
+ return register_rpmsg_driver(&rpmsg_tty_driver);
+}
+
+static void __exit fini(void)
+{
+ unregister_rpmsg_driver(&rpmsg_tty_driver);
+}
+module_init(init);
+module_exit(fini);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("iMX virtio remote processor messaging tty driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/vf610_rpmsg.c b/drivers/rpmsg/vf610_rpmsg.c
new file mode 100644
index 000000000000..70ff6ab5fab7
--- /dev/null
+++ b/drivers/rpmsg/vf610_rpmsg.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2016 Toradex AG
+ *
+ * Derived from the downstream iMX7 rpmsg implementation by Freescale.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/vf610_mscm.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/rpmsg.h>
+#include <linux/slab.h>
+#include <linux/vf610_sema4.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_ring.h>
+
+struct vf610_rpmsg_vproc {
+ struct virtio_device vdev;
+ unsigned int vring[2];
+ char *rproc_name;
+ struct mutex lock;
+ struct delayed_work rpmsg_work;
+ struct virtqueue *vq[2];
+ int base_vq_id;
+ int num_of_vqs;
+};
+
+#define MSCM_CPU_M4 1
+
+/*
+ * For now, allocate 256 buffers of 512 bytes for each side. each buffer
+ * will then have 16B for the msg header and 496B for the payload.
+ * This will require a total space of 256KB for the buffers themselves, and
+ * 3 pages for every vring (the size of the vring depends on the number of
+ * buffers it supports).
+ */
+#define RPMSG_NUM_BUFS (32)
+#define RPMSG_BUF_SIZE (512)
+#define RPMSG_BUFS_SPACE (RPMSG_NUM_BUFS * RPMSG_BUF_SIZE)
+
+/*
+ * The alignment between the consumer and producer parts of the vring.
+ * Note: this is part of the "wire" protocol. If you change this, you need
+ * to update your BIOS image as well
+ */
+#define RPMSG_VRING_ALIGN (4096)
+
+/* With 256 buffers, our vring will occupy 3 pages */
+#define RPMSG_RING_SIZE ((DIV_ROUND_UP(vring_size(RPMSG_NUM_BUFS / 2, \
+ RPMSG_VRING_ALIGN), PAGE_SIZE)) * PAGE_SIZE)
+
+#define to_vf610_rpdev(vd) container_of(vd, struct vf610_rpmsg_vproc, vdev)
+
+struct vf610_rpmsg_vq_info {
+ __u16 num; /* number of entries in the virtio_ring */
+ __u16 vq_id; /* a globaly unique index of this virtqueue */
+ void *addr; /* address where we mapped the virtio ring */
+ struct vf610_rpmsg_vproc *rpdev;
+};
+
+static struct vf610_sema4_mutex *rpmsg_mutex;
+
+static u64 vf610_rpmsg_get_features(struct virtio_device *vdev)
+{
+ return 1 << VIRTIO_RPMSG_F_NS;
+}
+
+static int vf610_rpmsg_finalize_features(struct virtio_device *vdev)
+{
+ /* Give virtio_ring a chance to accept features */
+ vring_transport_features(vdev);
+
+ return 0;
+}
+
+/* kick the remote processor */
+static bool vf610_rpmsg_notify(struct virtqueue *vq)
+{
+ struct vf610_rpmsg_vq_info *rpvq = vq->priv;
+
+ mutex_lock(&rpvq->rpdev->lock);
+
+ mscm_trigger_cpu2cpu_irq(rpvq->vq_id, MSCM_CPU_M4);
+
+ mutex_unlock(&rpvq->rpdev->lock);
+
+ return true;
+}
+
+static void rpmsg_work_handler(struct work_struct *work)
+{
+ struct vf610_rpmsg_vproc *rpdev = container_of(work,
+ struct vf610_rpmsg_vproc, rpmsg_work.work);
+
+ /* Process incoming buffers on all our vrings */
+ vring_interrupt(0, rpdev->vq[0]);
+ vring_interrupt(1, rpdev->vq[1]);
+}
+
+static irqreturn_t cpu_to_cpu_irq_handler(int irq, void *p)
+{
+ struct vf610_rpmsg_vproc *rpdev = (struct vf610_rpmsg_vproc *)p;
+
+ schedule_delayed_work(&rpdev->rpmsg_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
+ unsigned index,
+ void (*callback)(struct virtqueue *vq),
+ const char *name)
+{
+ struct vf610_rpmsg_vproc *rpdev = to_vf610_rpdev(vdev);
+ struct vf610_rpmsg_vq_info *rpvq;
+ struct virtqueue *vq;
+ int err;
+
+ rpvq = kmalloc(sizeof(*rpvq), GFP_KERNEL);
+ if (!rpvq)
+ return ERR_PTR(-ENOMEM);
+
+ /* ioremap'ing normal memory, so we cast away sparse's complaints */
+ rpvq->addr = (__force void *) ioremap_nocache(rpdev->vring[index],
+ RPMSG_RING_SIZE);
+ if (!rpvq->addr) {
+ err = -ENOMEM;
+ goto free_rpvq;
+ }
+
+ memset(rpvq->addr, 0, RPMSG_RING_SIZE);
+
+ pr_debug("vring%d: phys 0x%x, virt 0x%x\n", index, rpdev->vring[index],
+ (unsigned int) rpvq->addr);
+
+ vq = vring_new_virtqueue(index, RPMSG_NUM_BUFS, RPMSG_VRING_ALIGN,
+ vdev, true, rpvq->addr, vf610_rpmsg_notify, callback,
+ name);
+ if (!vq) {
+ pr_err("vring_new_virtqueue failed\n");
+ err = -ENOMEM;
+ goto unmap_vring;
+ }
+
+ rpdev->vq[index] = vq;
+ vq->priv = rpvq;
+ /* system-wide unique id for this virtqueue */
+ rpvq->vq_id = rpdev->base_vq_id + index;
+ rpvq->rpdev = rpdev;
+ mutex_init(&rpdev->lock);
+
+ return vq;
+
+unmap_vring:
+ /* iounmap normal memory, so make sparse happy */
+ iounmap((__force void __iomem *) rpvq->addr);
+free_rpvq:
+ kfree(rpvq);
+ return ERR_PTR(err);
+}
+
+static void vf610_rpmsg_del_vqs(struct virtio_device *vdev)
+{
+ struct virtqueue *vq, *n;
+
+ list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
+ struct vf610_rpmsg_vq_info *rpvq = vq->priv;
+ iounmap(rpvq->addr);
+ vring_del_virtqueue(vq);
+ kfree(rpvq);
+ }
+}
+
+static int vf610_rpmsg_find_vqs(struct virtio_device *vdev, unsigned nvqs,
+ struct virtqueue *vqs[],
+ vq_callback_t *callbacks[],
+ const char *names[])
+{
+ struct vf610_rpmsg_vproc *rpdev = to_vf610_rpdev(vdev);
+ int i, err;
+
+ /* we maintain two virtqueues per remote processor (for RX and TX) */
+ if (nvqs != 2)
+ return -EINVAL;
+
+ for (i = 0; i < nvqs; ++i) {
+ vqs[i] = rp_find_vq(vdev, i, callbacks[i], names[i]);
+ if (IS_ERR(vqs[i])) {
+ err = PTR_ERR(vqs[i]);
+ goto error;
+ }
+ }
+
+ rpdev->num_of_vqs = nvqs;
+
+ return 0;
+
+error:
+ vf610_rpmsg_del_vqs(vdev);
+ return err;
+}
+
+static void vf610_rpmsg_reset(struct virtio_device *vdev)
+{
+ dev_dbg(&vdev->dev, "reset !\n");
+}
+
+static u8 vf610_rpmsg_get_status(struct virtio_device *vdev)
+{
+ return 0;
+}
+
+static void vf610_rpmsg_set_status(struct virtio_device *vdev, u8 status)
+{
+ dev_dbg(&vdev->dev, "%s new status: %d\n", __func__, status);
+}
+
+static void vf610_rpmsg_vproc_release(struct device *dev)
+{
+ /* this handler is provided so driver core doesn't yell at us */
+}
+
+static struct virtio_config_ops vf610_rpmsg_config_ops = {
+ .get_features = vf610_rpmsg_get_features,
+ .finalize_features = vf610_rpmsg_finalize_features,
+ .find_vqs = vf610_rpmsg_find_vqs,
+ .del_vqs = vf610_rpmsg_del_vqs,
+ .reset = vf610_rpmsg_reset,
+ .set_status = vf610_rpmsg_set_status,
+ .get_status = vf610_rpmsg_get_status,
+};
+
+static struct vf610_rpmsg_vproc vf610_rpmsg_vprocs = {
+ .vdev.id.device = VIRTIO_ID_RPMSG,
+ .vdev.config = &vf610_rpmsg_config_ops,
+ .rproc_name = "vf610_m4",
+ .base_vq_id = 0,
+};
+
+static const struct of_device_id vf610_rpmsg_dt_ids[] = {
+ { .compatible = "fsl,vf610-rpmsg", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vf610_rpmsg_dt_ids);
+
+static int vf610_rpmsg_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret = 0;
+ struct vf610_rpmsg_vproc *rpdev = &vf610_rpmsg_vprocs;
+
+ INIT_DELAYED_WORK(&rpdev->rpmsg_work, rpmsg_work_handler);
+
+ rpdev->vring[0] = 0x3f070000;
+ rpdev->vring[1] = 0x3f074000;
+
+ rpdev->vdev.dev.parent = &pdev->dev;
+ rpdev->vdev.dev.release = vf610_rpmsg_vproc_release;
+
+ ret = register_virtio_device(&rpdev->vdev);
+ if (ret) {
+ pr_err("%s failed to register rpdev: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ for (i = 0; i < 2; i++) {
+ ret = mscm_request_cpu2cpu_irq(i, cpu_to_cpu_irq_handler,
+ (const char *)rpdev->rproc_name, rpdev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to register CPU2CPU interrupt\n");
+ unregister_virtio_device(&rpdev->vdev);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int vf610_rpmsg_remove(struct platform_device *pdev)
+{
+ struct vf610_rpmsg_vproc *rpdev = &vf610_rpmsg_vprocs;
+ int i;
+
+ for (i = 0; i < 2; i++)
+ mscm_free_cpu2cpu_irq(i, NULL);
+
+ unregister_virtio_device(&rpdev->vdev);
+
+ return 0;
+}
+
+static struct platform_driver vf610_rpmsg_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "vf610-rpmsg",
+ .of_match_table = vf610_rpmsg_dt_ids,
+ },
+ .probe = vf610_rpmsg_probe,
+ .remove = vf610_rpmsg_remove,
+};
+
+static int __init vf610_rpmsg_init(void)
+{
+ int ret;
+
+ rpmsg_mutex = vf610_sema4_mutex_create(0, 0);
+ if (!rpmsg_mutex) {
+ pr_err("vf610 rpmsg unable to create mutex\n");
+ return -EINVAL;
+ }
+
+ vf610_sema4_mutex_lock(rpmsg_mutex);
+
+ ret = platform_driver_register(&vf610_rpmsg_driver);
+ if (ret)
+ pr_err("Unable to initialize rpmsg driver\n");
+ else
+ pr_info("vf610 rpmsg driver is registered.\n");
+
+ vf610_sema4_mutex_unlock(rpmsg_mutex);
+
+ return ret;
+}
+
+static void __exit vf610_rpmsg_exit(void)
+{
+ if (rpmsg_mutex)
+ vf610_sema4_mutex_destroy(rpmsg_mutex);
+
+ pr_info("vf610 rpmsg driver is unregistered.\n");
+ platform_driver_unregister(&vf610_rpmsg_driver);
+}
+module_exit(vf610_rpmsg_exit);
+module_init(vf610_rpmsg_init);
+
+MODULE_AUTHOR("Sanchayan Maity <sanchayan.maity@toradex.com>");
+MODULE_DESCRIPTION("vf610 remote processor messaging virtio device");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/vf610_mscm.h b/include/linux/vf610_mscm.h
new file mode 100644
index 000000000000..c551b50e700c
--- /dev/null
+++ b/include/linux/vf610_mscm.h
@@ -0,0 +1,13 @@
+#ifndef __VF610_MSCM__
+#define __VF610_MSCM__
+
+#include <linux/interrupt.h>
+
+int mscm_request_cpu2cpu_irq(unsigned int intid, irq_handler_t handler,
+ const char *name, void *priv);
+void mscm_free_cpu2cpu_irq(unsigned int intid, void *priv);
+void mscm_trigger_cpu2cpu_irq(unsigned int intid, int cpuid);
+void mscm_enable_cpu2cpu_irq(unsigned int intid);
+void mscm_disable_cpu2cpu_irq(unsigned int intid);
+
+#endif /* __VF610_MSCM__ */
diff --git a/include/linux/vf610_sema4.h b/include/linux/vf610_sema4.h
new file mode 100644
index 000000000000..a57de2303e9a
--- /dev/null
+++ b/include/linux/vf610_sema4.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 Toradex AG.
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_VF610_SEMA4_H__
+#define __LINUX_VF610_SEMA4_H__
+
+#define SEMA4_NUM_DEVICES 1
+#define SEMA4_NUM_GATES 16
+
+#define SEMA4_UNLOCK 0x00
+#define SEMA4_A5_LOCK 0x01
+#define SEMA4_GATE_MASK 0x03
+
+#define CORE_MUTEX_VALID (('c'<<24)|('m'<<24)|('t'<<24)|'x')
+
+/*
+ * The enumerates
+ */
+enum {
+ /* sema4 registers offset */
+ SEMA4_CP0INE = 0x42,
+ SEMA4_CP1INE = 0x4A,
+ SEMA4_CP0NTF = 0x82,
+ SEMA4_CP1NTF = 0x8A,
+};
+
+static const unsigned int idx_sema4[SEMA4_NUM_GATES] = {
+ 1 << 15, 1 << 14, 1 << 13, 1 << 12,
+ 1 << 11, 1 << 10, 1 << 9, 1 << 8,
+ 1 << 7, 1 << 6, 1 << 5, 1 << 4,
+ 1 << 3, 1 << 2, 1 << 1, 1 << 0,
+};
+
+struct vf610_sema4_mutex {
+ u32 valid;
+ u32 gate_num;
+ unsigned char gate_val;
+ wait_queue_head_t wait_q;
+};
+
+struct vf610_sema4_mutex_device {
+ struct device *dev;
+ u16 cpntf_val;
+ u16 cpine_val;
+ void __iomem *ioaddr; /* Mapped address */
+ spinlock_t lock; /* Mutex */
+ int irq;
+
+ u16 alloced;
+ struct vf610_sema4_mutex *mutex_ptr[SEMA4_NUM_GATES];
+};
+
+struct vf610_sema4_mutex *
+ vf610_sema4_mutex_create(u32 dev_num, u32 mutex_num);
+#ifdef CONFIG_VF610_SEMA4
+int vf610_sema4_mutex_destroy(struct vf610_sema4_mutex *mutex_ptr);
+int vf610_sema4_mutex_trylock(struct vf610_sema4_mutex *mutex_ptr);
+int vf610_sema4_mutex_lock(struct vf610_sema4_mutex *mutex_ptr);
+int vf610_sema4_mutex_unlock(struct vf610_sema4_mutex *mutex_ptr);
+#else
+static inline int vf610_sema4_mutex_destroy(struct vf610_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+static inline int vf610_sema4_mutex_trylock(struct vf610_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+static inline int vf610_sema4_mutex_lock(struct vf610_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+static inline int vf610_sema4_mutex_unlock(struct vf610_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+#endif
+#endif /* __LINUX_VF610_SEMA4_H__ */