diff options
author | Stefan Agner <stefan.agner@toradex.com> | 2016-08-24 11:25:37 -0700 |
---|---|---|
committer | Stefan Agner <stefan.agner@toradex.com> | 2016-09-29 13:57:42 -0700 |
commit | 8f2dbc30ed0274c960b9a29086d677e111551e5a (patch) | |
tree | 38e9c0229b119f2324ebc5e26ab91d8d793fd317 | |
parent | e11f8ba48c0767f787067a382ddda707109d9305 (diff) | |
parent | 8439c80d3611af210ce03fbc9d4e4571b340b0fe (diff) |
Merge branch 'toradex_vf_4.4-rpmsg' into toradex_vf_4.4-next
-rw-r--r-- | arch/arm/boot/dts/vf610.dtsi | 27 | ||||
-rw-r--r-- | arch/arm/boot/dts/vfxxx.dtsi | 40 | ||||
-rw-r--r-- | arch/arm/configs/colibri_vf_defconfig | 6 | ||||
-rw-r--r-- | drivers/char/Kconfig | 6 | ||||
-rw-r--r-- | drivers/char/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/vf610_sema4.c | 322 | ||||
-rw-r--r-- | drivers/irqchip/irq-vf610-mscm-ir.c | 126 | ||||
-rw-r--r-- | drivers/remoteproc/Kconfig | 9 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 1 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 48 | ||||
-rw-r--r-- | drivers/remoteproc/vf610_cm4_rproc.c | 259 | ||||
-rw-r--r-- | drivers/rpmsg/Kconfig | 12 | ||||
-rw-r--r-- | drivers/rpmsg/Makefile | 3 | ||||
-rw-r--r-- | drivers/rpmsg/imx_rpmsg_pingpong.c | 110 | ||||
-rw-r--r-- | drivers/rpmsg/imx_rpmsg_tty.c | 233 | ||||
-rw-r--r-- | drivers/rpmsg/vf610_rpmsg.c | 353 | ||||
-rw-r--r-- | include/linux/vf610_mscm.h | 13 | ||||
-rw-r--r-- | include/linux/vf610_sema4.h | 83 |
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__ */ |